Atualizar todos os objetos em uma coleção usando o LINQ

500

Existe uma maneira de fazer o seguinte usando o LINQ?

foreach (var c in collection)
{
    c.PropertyToSet = value;
}

Para esclarecer, desejo percorrer cada objeto em uma coleção e atualizar uma propriedade em cada objeto.

Meu caso de uso é que tenho vários comentários em uma postagem do blog e quero percorrer cada comentário em uma postagem do blog e definir a data e hora da postagem do blog como +10 horas. Eu poderia fazer isso no SQL, mas quero mantê-lo na camada de negócios.

lomaxx
fonte
14
Pergunta interessante. Pessoalmente, prefiro o que você tem acima - muito mais claro o que está acontecendo!
Noelicus
8
Eu vim aqui procurando uma resposta para a mesma pergunta e decidi que era tão fácil, menos código e mais fácil de entender para futuros desenvolvedores fazerem exatamente como você fez no seu OP.
Casey Crookston
4
Por que você deseja fazer isso no LINQ?
Caltor
13
Esta questão pede a coisa errada, a resposta só correta é: não usar LINQ para modificar a fonte de dados
Tim Schmelter
Estou votando para encerrar esta questão como fora de tópico, porque quase todas as respostas a essa pergunta são ativamente prejudiciais ao entendimento dos novos programadores do LINQ.
Tanveer Badar 23/03

Respostas:

842

Embora você possa usar um ForEachmétodo de extensão, se quiser usar apenas a estrutura, poderá

collection.Select(c => {c.PropertyToSet = value; return c;}).ToList();

A ToListé necessária, a fim de avaliar a escolha de imediato devido à avaliação preguiçosa .

Cameron MacFarland
fonte
6
Votei positivo porque é uma solução muito boa ... a única razão pela qual eu gosto do método de extensão é que fica um pouco mais claro para entender exatamente o que está acontecendo ... no entanto, sua solução ainda é muito boa
lomaxx
9
Se a coleção foi uma ObservableCollectionpalavra a dizer, pode ser útil alterar os itens no lugar, em vez de criar uma nova lista.
Cameron MacFarland
7
@desaivv sim, isso é um pouco de abuso de sintaxe, então o Resharper está avisando sobre isso.
Cameron MacFarland
46
IMHO, isso é muito menos expressivo do que um simples loop foreach. O ToList () é confuso porque não é usado para nada, mas para forçar a avaliação que, de outra forma, seria adiada. A projeção também é confusa porque não é usada para a finalidade pretendida; em vez disso, é usado para iterar os elementos da coleção e permitir o acesso a uma propriedade para que ela possa ser atualizada. A única pergunta em minha mente seria se o loop foreach poderia ou não se beneficiar do paralelismo usando Parallel.ForEach, mas essa é uma pergunta diferente.
Philippe
37
Esta resposta é uma pior prática. Nunca faça isso.
Eric Lippert
351
collection.ToList().ForEach(c => c.PropertyToSet = value);
Ε Г И І И О
fonte
36
@SanthoshKumar: Usecollection.ToList().ForEach(c => { c.Property1ToSet = value1; c.Property2ToSet = value2; });
Ε Г И І О
@CameronMacFarland: Claro que não, pois as estruturas são imutáveis. Mas se você realmente quiser, pode fazer o seguinte: #: #collection.ToList().ForEach(c => { collection[collection.IndexOf(c)] = new <struct type>() { <propertyToSet> = value, <propertyToRetain> = c.Property2Retain }; });
10/13 /
11
Isso tem a vantagem sobre a resposta de Cameron MacFarland de atualizar a lista no lugar, em vez de criar uma nova lista.
Simon Tewsi
7
Uau, essa resposta realmente não é útil. Criando uma nova coleção apenas para ser capaz de usar um loop
Tim Schmelter
@SimonTewsi Como é uma coleção de objetos, a lista deve ser atualizada de qualquer maneira. A coleção será nova, mas os objetos na coleção serão os mesmos.
Chris
70

Estou fazendo isso

Collection.All(c => { c.needsChange = value; return true; });
Rahul
fonte
Eu acho que essa é a maneira mais limpa de fazer isso.
wcm
31
Essa abordagem certamente funciona, mas viola a intenção do All()método de extensão, levando a possíveis confusões quando alguém lê o código.
Tom Baxter
Esta abordagem é melhor .Usando Tudo em vez de usar cada loop
UJS
2
Definitivamente, prefira isso a chamar ToList () desnecessariamente, mesmo que seja um pouco enganador sobre o que está usando All ().
iupchris10
1
Se você estiver usando uma coleção como List<>essa, o ForEach()método é uma maneira muito menos enigmática de fazer isso. exForEach(c => { c.needsChange = value; })
Dan Is Fiddling Por Firelight
27

Na verdade, eu encontrei um método de extensão que fará o que eu quero bem

public static IEnumerable<T> ForEach<T>(
    this IEnumerable<T> source,
    Action<T> act)
{
    foreach (T element in source) act(element);
    return source;
}
lomaxx
fonte
4
nice :) Lomaxx, talvez adicione um exemplo para que as pessoas possam vê-lo em 'ação' (boom tish!).
Pure.Krome
2
Essa é a única abordagem útil se você realmente deseja evitar um foreachloop (por qualquer motivo).
amigos estão dizendo sobre tim Schmelter
@Rango que você ainda não está evitando foreachque o próprio código contém o foreachcircuito
GoldBishop
@GoldBishop claro, o método oculta o loop.
Tim Schmelter
1
O link está quebrado, agora está disponível em: codewrecks.com/blog/index.php/2008/08/13/… . Há também um comentário no blog que é vinculado a stackoverflow.com/questions/200574 . Por sua vez, o principal comentário da pergunta está vinculado a blogs.msdn.microsoft.com/ericlippert/2009/05/18/… . Talvez a resposta seja mais simples reescrita usando o MSDN (você ainda pode creditar o primeiro link, se quiser). Sidenote: Rust possui recursos semelhantes e, eventualmente, cedeu e adicionou a função equivalente: stackoverflow.com/a/50224248/799204
sourcejedi
15

Usar:

ListOfStuff.Where(w => w.Thing == value).ToList().ForEach(f => f.OtherThing = vauleForNewOtherThing);

Não tenho certeza se isso está usando muito o LINQ ou não, mas funcionou para mim ao querer atualizar itens específicos da lista para uma condição específica.

Hennish
fonte
7

Não há método de extensão interno para fazer isso. Embora definir um seja bastante direto. Na parte inferior da postagem há um método que eu defini chamado Iterate. Pode ser usado assim

collection.Iterate(c => { c.PropertyToSet = value;} );

Iterar Origem

public static void Iterate<T>(this IEnumerable<T> enumerable, Action<T> callback)
{
    if (enumerable == null)
    {
        throw new ArgumentNullException("enumerable");
    }

    IterateHelper(enumerable, (x, i) => callback(x));
}

public static void Iterate<T>(this IEnumerable<T> enumerable, Action<T,int> callback)
{
    if (enumerable == null)
    {
        throw new ArgumentNullException("enumerable");
    }

    IterateHelper(enumerable, callback);
}

private static void IterateHelper<T>(this IEnumerable<T> enumerable, Action<T,int> callback)
{
    int count = 0;
    foreach (var cur in enumerable)
    {
        callback(cur, count);
        count++;
    }
}
JaredPar
fonte
A iteração é necessária, o que há de errado com Count, Sum, Avg ou outro método de extensão existente que retorna um valor escalar?
AnthonyWJones
2
isso é bem próximo do que eu quero, mas um pouco ... envolvido. A postagem do blog que publiquei tem uma implementação semelhante, mas com menos linhas de código.
lomaxx
1
O IterateHelper parece um exagero. A sobrecarga que não leva um índice acaba fazendo muito mais trabalho extra (converta o retorno de chamada para lambda, que leva o índice, mantenha uma contagem que nunca é usada). Entendo que é reutilização, mas é uma solução alternativa para o uso de um forloop de qualquer maneira, para que seja eficiente.
Cameron MacFarland
2
@Cameron, IterateHelper serve para dois propósitos. 1) Implementação única e 2) permite que ArgumentNullException seja lançada no momento da chamada versus uso. Os iteradores C # são executados com atraso, sendo que o auxiliar impede o comportamento estranho de uma exceção ser lançada durante a iteração.
JaredPar
2
@ JaredPar: Exceto que você não está usando um iterador. Não há declaração de rendimento.
Cameron MacFarland
7

Embora você tenha solicitado especificamente uma solução LINQ e essa pergunta seja bastante antiga, eu posto uma solução não-LINQ. Isso ocorre porque o LINQ (= consulta integrada ao idioma ) deve ser usado para consultas em coleções. Todos os métodos LINQ não modificam a coleção subjacente, apenas retornam uma nova (ou um iterador mais preciso para uma nova coleção). Assim, o que você faz, por exemplo, com a Selectnão afeta a coleção subjacente, você simplesmente obtém uma nova.

Claro que você pode fazer isso com um ForEach(que não é LINQ, a propósito, mas uma extensão ativada List<T>). Mas isso literalmente usa de foreachqualquer maneira, mas com uma expressão lambda. Além disso, todo método LINQ interage internamente sua coleção, por exemplo, usando foreachoufor , no entanto, simplesmente a oculta do cliente. Eu não considero isso mais legível nem sustentável (pense em editar seu código enquanto depura um método que contém expressões lambda).

Dito isto, não deve usar o LINQ para modificar itens em sua coleção. Uma maneira melhor é a solução que você já forneceu na sua pergunta. Com um loop clássico, você pode facilmente iterar sua coleção e atualizar seus itens. De fato, todas essas soluções baseadas emList.ForEach não são nada diferentes, mas muito mais difíceis de ler da minha perspectiva.

Portanto, você não deve usar o LINQ nos casos em que deseja atualizar os elementos da sua coleção.

HimBromBeere
fonte
3
Fora do tópico: eu concordo, e existem muuuuito muitas instâncias de LINQ sendo abusadas, exemplos de pessoas solicitando "cadeias LINQ de alto desempenho", para fazer o que poderia ser realizado com um único loop etc. Estou agradecido por NÃO usar o LINQ. muito arraigado em mim e normalmente não o usa. Vejo pessoas usando cadeias LINQ para executar uma única ação, sem perceber que praticamente toda vez que um comando LINQ é usado, você está criando outro forloop "sob o capô". Eu acho que é um açúcar sintático criar maneiras menos detalhadas de realizar tarefas simples, para não substituir a codificação padrão.
precisa
6

Eu tentei algumas variações e continuo voltando à solução desse cara.

http://www.hookedonlinq.com/UpdateOperator.ashx

Novamente, esta é a solução de outra pessoa. Mas compilei o código em uma pequena biblioteca e o uso com bastante regularidade.

Vou colar o código dele aqui, para a chance de que seu site (blog) deixe de existir em algum momento no futuro. (Não há nada pior do que ver uma postagem que diz "Aqui está a resposta exata de que você precisa", Clique e URL morto.)

    public static class UpdateExtensions {

    public delegate void Func<TArg0>(TArg0 element);

    /// <summary>
    /// Executes an Update statement block on all elements in an IEnumerable<T> sequence.
    /// </summary>
    /// <typeparam name="TSource">The source element type.</typeparam>
    /// <param name="source">The source sequence.</param>
    /// <param name="update">The update statement to execute for each element.</param>
    /// <returns>The numer of records affected.</returns>
    public static int Update<TSource>(this IEnumerable<TSource> source, Func<TSource> update)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (update == null) throw new ArgumentNullException("update");
        if (typeof(TSource).IsValueType)
            throw new NotSupportedException("value type elements are not supported by update.");

        int count = 0;
        foreach (TSource element in source)
        {
            update(element);
            count++;
        }
        return count;
    }
}



int count = drawingObjects
        .Where(d => d.IsSelected && d.Color == Colors.Blue)
        .Update(e => { e.Color = Color.Red; e.Selected = false; } );
granadaCoder
fonte
1
Você pode usar um em Action<TSource>vez de criar um delegado extra. Isso pode não estar disponível no momento em que escrevemos isso.
Frank J
Sim, era o DotNet da velha escola naquele momento. Bom comentário Frank.
usar o seguinte código
O URL está morto! (Este nome de domínio expirou) Ainda bem que copiei o código aqui! #patOnShoulder
granadaCoder
1
A verificação do tipo de valor faz sentido, mas talvez seja melhor usar uma restrição, ou seja where T: struct, capturar isso em tempo de compilação.
Groo
3

Eu escrevi alguns métodos de extensão para me ajudar com isso.

namespace System.Linq
{
    /// <summary>
    /// Class to hold extension methods to Linq.
    /// </summary>
    public static class LinqExtensions
    {
        /// <summary>
        /// Changes all elements of IEnumerable by the change function
        /// </summary>
        /// <param name="enumerable">The enumerable where you want to change stuff</param>
        /// <param name="change">The way you want to change the stuff</param>
        /// <returns>An IEnumerable with all changes applied</returns>
        public static IEnumerable<T> Change<T>(this IEnumerable<T> enumerable, Func<T, T> change  )
        {
            ArgumentCheck.IsNullorWhiteSpace(enumerable, "enumerable");
            ArgumentCheck.IsNullorWhiteSpace(change, "change");

            foreach (var item in enumerable)
            {
                yield return change(item);
            }
        }

        /// <summary>
        /// Changes all elements of IEnumerable by the change function, that fullfill the where function
        /// </summary>
        /// <param name="enumerable">The enumerable where you want to change stuff</param>
        /// <param name="change">The way you want to change the stuff</param>
        /// <param name="where">The function to check where changes should be made</param>
        /// <returns>
        /// An IEnumerable with all changes applied
        /// </returns>
        public static IEnumerable<T> ChangeWhere<T>(this IEnumerable<T> enumerable, 
                                                    Func<T, T> change,
                                                    Func<T, bool> @where)
        {
            ArgumentCheck.IsNullorWhiteSpace(enumerable, "enumerable");
            ArgumentCheck.IsNullorWhiteSpace(change, "change");
            ArgumentCheck.IsNullorWhiteSpace(@where, "where");

            foreach (var item in enumerable)
            {
                if (@where(item))
                {
                    yield return change(item);
                }
                else
                {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// Changes all elements of IEnumerable by the change function that do not fullfill the except function
        /// </summary>
        /// <param name="enumerable">The enumerable where you want to change stuff</param>
        /// <param name="change">The way you want to change the stuff</param>
        /// <param name="where">The function to check where changes should not be made</param>
        /// <returns>
        /// An IEnumerable with all changes applied
        /// </returns>
        public static IEnumerable<T> ChangeExcept<T>(this IEnumerable<T> enumerable,
                                                     Func<T, T> change,
                                                     Func<T, bool> @where)
        {
            ArgumentCheck.IsNullorWhiteSpace(enumerable, "enumerable");
            ArgumentCheck.IsNullorWhiteSpace(change, "change");
            ArgumentCheck.IsNullorWhiteSpace(@where, "where");

            foreach (var item in enumerable)
            {
                if (!@where(item))
                {
                    yield return change(item);
                }
                else
                {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// Update all elements of IEnumerable by the update function (only works with reference types)
        /// </summary>
        /// <param name="enumerable">The enumerable where you want to change stuff</param>
        /// <param name="update">The way you want to change the stuff</param>
        /// <returns>
        /// The same enumerable you passed in
        /// </returns>
        public static IEnumerable<T> Update<T>(this IEnumerable<T> enumerable,
                                               Action<T> update) where T : class
        {
            ArgumentCheck.IsNullorWhiteSpace(enumerable, "enumerable");
            ArgumentCheck.IsNullorWhiteSpace(update, "update");
            foreach (var item in enumerable)
            {
                update(item);
            }
            return enumerable;
        }

        /// <summary>
        /// Update all elements of IEnumerable by the update function (only works with reference types)
        /// where the where function returns true
        /// </summary>
        /// <param name="enumerable">The enumerable where you want to change stuff</param>
        /// <param name="update">The way you want to change the stuff</param>
        /// <param name="where">The function to check where updates should be made</param>
        /// <returns>
        /// The same enumerable you passed in
        /// </returns>
        public static IEnumerable<T> UpdateWhere<T>(this IEnumerable<T> enumerable,
                                               Action<T> update, Func<T, bool> where) where T : class
        {
            ArgumentCheck.IsNullorWhiteSpace(enumerable, "enumerable");
            ArgumentCheck.IsNullorWhiteSpace(update, "update");
            foreach (var item in enumerable)
            {
                if (where(item))
                {
                    update(item);
                }
            }
            return enumerable;
        }

        /// <summary>
        /// Update all elements of IEnumerable by the update function (only works with reference types)
        /// Except the elements from the where function
        /// </summary>
        /// <param name="enumerable">The enumerable where you want to change stuff</param>
        /// <param name="update">The way you want to change the stuff</param>
        /// <param name="where">The function to check where changes should not be made</param>
        /// <returns>
        /// The same enumerable you passed in
        /// </returns>
        public static IEnumerable<T> UpdateExcept<T>(this IEnumerable<T> enumerable,
                                               Action<T> update, Func<T, bool> where) where T : class
        {
            ArgumentCheck.IsNullorWhiteSpace(enumerable, "enumerable");
            ArgumentCheck.IsNullorWhiteSpace(update, "update");

            foreach (var item in enumerable)
            {
                if (!where(item))
                {
                    update(item);
                }
            }
            return enumerable;
        }
    }
}

Estou usando assim:

        List<int> exampleList = new List<int>()
            {
                1, 2 , 3
            };

        //2 , 3 , 4
        var updated1 = exampleList.Change(x => x + 1);

        //10, 2, 3
        var updated2 = exampleList
            .ChangeWhere(   changeItem => changeItem * 10,          // change you want to make
                            conditionItem => conditionItem < 2);    // where you want to make the change

        //1, 0, 0
        var updated3 = exampleList
            .ChangeExcept(changeItem => 0,                          //Change elements to 0
                          conditionItem => conditionItem == 1);     //everywhere but where element is 1

Para referência ao argumento, verifique:

/// <summary>
/// Class for doing argument checks
/// </summary>
public static class ArgumentCheck
{


    /// <summary>
    /// Checks if a value is string or any other object if it is string
    /// it checks for nullorwhitespace otherwhise it checks for null only
    /// </summary>
    /// <typeparam name="T">Type of the item you want to check</typeparam>
    /// <param name="item">The item you want to check</param>
    /// <param name="nameOfTheArgument">Name of the argument</param>
    public static void IsNullorWhiteSpace<T>(T item, string nameOfTheArgument = "")
    {

        Type type = typeof(T);
        if (type == typeof(string) ||
            type == typeof(String))
        {
            if (string.IsNullOrWhiteSpace(item as string))
            {
                throw new ArgumentException(nameOfTheArgument + " is null or Whitespace");
            }
        }
        else
        {
            if (item == null)
            {
                throw new ArgumentException(nameOfTheArgument + " is null");
            }
        }

    }
}
PartTimeIndie
fonte
2

Meus 2 centavos: -

 collection.Count(v => (v.PropertyToUpdate = newValue) == null);
AnthonyWJones
fonte
7
I como o pensamento, mas não é realmente claro o que o código está fazendo
lomaxx
2

Você pode usar o LINQ para converter sua coleção em uma matriz e depois chamar Array.ForEach ():

Array.ForEach(MyCollection.ToArray(), item=>item.DoSomeStuff());

Obviamente, isso não funcionará com coleções de estruturas ou tipos embutidos, como números inteiros ou cadeias.

Tamas Czinege
fonte
1

Aqui está o método de extensão que eu uso ...

    /// <summary>
    /// Executes an Update statement block on all elements in an  IEnumerable of T
    /// sequence.
    /// </summary>
    /// <typeparam name="TSource">The source element type.</typeparam>
    /// <param name="source">The source sequence.</param>
    /// <param name="action">The action method to execute for each element.</param>
    /// <returns>The number of records affected.</returns>
    public static int Update<TSource>(this IEnumerable<TSource> source, Func<TSource> action)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (action == null) throw new ArgumentNullException("action");
        if (typeof (TSource).IsValueType)
            throw new NotSupportedException("value type elements are not supported by update.");

        var count = 0;
        foreach (var element in source)
        {
            action(element);
            count++;
        }
        return count;
    }
Bill Forney
fonte
Por que "elementos do tipo valor não são suportados pela atualização"? Nada interfere nisso!
precisa saber é o seguinte
Isso foi específico para o projeto em que eu estava trabalhando. Suponho que isso não importaria na maioria dos casos. Ultimamente eu refiz isso e renomei-o para Executar (...), removi o tipo de valor e o alterei para retornar nulo e soltou o código de contagem.
Bill Forney
É mais ou menos o que List<T>.ForEachtambém faz, mas apenas para todos IEnumerable.
HimBromBeere
Olhando para trás agora, eu diria que basta usar um loop foreach. O único benefício de usar algo assim é se você deseja encadear os métodos juntos e retornar o enumerável da função para continuar a encadernação após executar a ação. Caso contrário, isso é apenas uma chamada de método extra sem nenhum benefício.
Bill Forney
0

Eu suponho que você queira alterar valores dentro de uma consulta para poder escrever uma função para ela

void DoStuff()
{
    Func<string, Foo, bool> test = (y, x) => { x.Bar = y; return true; };
    List<Foo> mylist = new List<Foo>();
    var v = from x in mylist
            where test("value", x)
            select x;
}

class Foo
{
    string Bar { get; set; }
}

Mas não se preocupe se é isso que você quer dizer.

Stormenet
fonte
Isso está indo na direção certa, exigiria algo para enumerar v; caso contrário, não fará nada.
AnthonyWJones
0

Você pode usar o Magiq , uma estrutura de operação em lote para o LINQ.

ivos
fonte
-3

Suponha que tenhamos dados como abaixo,

var items = new List<string>({"123", "456", "789"});
// Like 123 value get updated to 123ABC ..

e se quisermos modificar a lista e substituir os valores existentes da lista por valores modificados, primeiro crie uma nova lista vazia e, em seguida, percorra a lista de dados chamando o método de modificação em cada item da lista,

var modifiedItemsList = new List<string>();

items.ForEach(i => {
  var modifiedValue = ModifyingMethod(i);
  modifiedItemsList.Add(items.AsEnumerable().Where(w => w == i).Select(x => modifiedValue).ToList().FirstOrDefault()?.ToString()) 
});
// assign back the modified list
items = modifiedItemsList;
Vishwa G
fonte
2
Por que você faria algo que pode ser executado no tempo de execução O (n) executado em O (n ^ 2) ou pior? Não estou ciente de como as especificidades do linq funcionam, mas posso ver que isso é, no mínimo, uma solução ^ 2 para um problema n .
Fallenreaper 27/03/19