Aggiorna tutti gli oggetti in una raccolta utilizzando LINQ


500

C'è un modo per fare quanto segue usando LINQ?

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

Per chiarire, voglio scorrere tutti gli oggetti in una raccolta e quindi aggiornare una proprietà su ciascun oggetto.

Il mio caso d'uso è che ho un sacco di commenti su un post di blog e voglio scorrere tutti i commenti su un post di blog e impostare il datetime sul post di blog su +10 ore. Potrei farlo in SQL, ma voglio mantenerlo nel livello aziendale.


14
Domanda interessante. Personalmente preferisco il modo in cui lo hai sopra - molto più chiaro cosa sta succedendo!
Noelicus,

8
Sono venuto qui alla ricerca di una risposta alla stessa domanda, e ho deciso che era altrettanto facile, meno codice e più facile da capire per i futuri sviluppatori farlo nel modo in cui hai fatto nel tuo OP.
Casey Crookston,

4
Perché vorresti farlo in LINQ?
Caltor,

13
Questa domanda fa la cosa sbagliata, l'unica risposta corretta è: non usare LINQ per modificare l'origine dati
Tim Schmelter,

Sto votando per chiudere questa domanda come fuori tema perché quasi tutte le risposte a questa domanda sono attivamente dannose per la comprensione di LINQ da parte dei nuovi programmatori.
Tanveer Badar,

Risposte:


842

Mentre puoi usare un ForEachmetodo di estensione, se vuoi usare solo il framework che puoi fare

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

Il ToListè necessaria al fine di valutare la selezionare immediatamente a causa di valutazione pigra .


6
Ho votato a favore perché è una soluzione piuttosto bella ... l'unica ragione per cui mi piace il metodo di estensione è che rende un po 'più chiaro capire esattamente cosa sta succedendo ... comunque la tua soluzione è ancora piuttosto dolce
lomaxx

9
Se la raccolta è stata una ObservableCollectionparola d'ordine, può essere utile cambiare gli elementi anziché creare un nuovo elenco.
Cameron MacFarland

7
@desaivv sì, questo è un po 'un abuso di sintassi, quindi Resharper ti sta avvertendo di questo.
Cameron MacFarland il

46
IMHO, questo è molto meno espressivo di un semplice ciclo foreach. ToList () è fonte di confusione perché non viene utilizzato per altro che forzare una valutazione che verrebbe altrimenti rinviata. La proiezione è anche confusa perché non viene utilizzata per lo scopo previsto; piuttosto, viene utilizzato per scorrere gli elementi della raccolta e consentire l'accesso a una proprietà in modo che possa essere aggiornata. L'unica domanda nella mia mente sarebbe se il ciclo foreach potesse trarre beneficio dal parallelismo usando Parallel.ForEach, ma questa è una domanda diversa.
Philippe,

37
Questa risposta è la peggiore pratica. Non farlo mai.
Eric Lippert,

351
collection.ToList().ForEach(c => c.PropertyToSet = value);

36
@SanthoshKumar: Usacollection.ToList().ForEach(c => { c.Property1ToSet = value1; c.Property2ToSet = value2; });
Ε Г И І И О

@CameronMacFarland: Certo che no, poiché le strutture sono immutabili. Ma se vuoi davvero, puoi farlo:collection.ToList().ForEach(c => { collection[collection.IndexOf(c)] = new <struct type>() { <propertyToSet> = value, <propertyToRetain> = c.Property2Retain }; });
Ε Г И І И О

11
Questo ha il vantaggio rispetto alla risposta di Cameron MacFarland di aggiornare l'elenco in atto, piuttosto che creare un nuovo elenco.
Simon Tewsi,

7
Caspita, questa risposta non è davvero utile. Creazione di una nuova raccolta solo per poter utilizzare un ciclo
Tim Schmelter,

@SimonTewsi Poiché si tratta di una raccolta di oggetti, l'elenco deve essere comunque aggiornato in posizione. La raccolta sarà nuova, ma gli oggetti nella raccolta saranno gli stessi.
Chris,

70

Lo sto facendo

Collection.All(c => { c.needsChange = value; return true; });

Penso che questo sia il modo più pulito per farlo.
wcm,

31
Questo approccio sicuramente funziona ma viola l'intento del All()metodo di estensione, creando potenziale confusione quando qualcun altro legge il codice.
Tom Baxter,

Questo approccio è migliore. Usando Tutto invece di usare ogni ciclo
UJS

2
Sicuramente preferisco questo piuttosto che chiamare ToList () inutilmente, anche se è un po 'fuorviante per quello che sta usando All ().
iupchris10,

1
Se stai usando una raccolta del genere List<>, il ForEach()metodo è un modo molto meno criptico per ottenere questo risultato. exForEach(c => { c.needsChange = value; })
Dan Is Fiddling By Firelight

27

In realtà ho trovato un metodo di estensione che farà quello che voglio bene

public static IEnumerable<T> ForEach<T>(
    this IEnumerable<T> source,
    Action<T> act)
{
    foreach (T element in source) act(element);
    return source;
}

4
bello :) Lomaxx, forse aggiungi un esempio in modo che i pigoli possano vederlo in 'azione' (boom tish!).
Pure.Krome,

2
Questo è l'unico approccio utile se vuoi davvero evitare un foreach-loop (per qualsiasi motivo).
Tim Schmelter,

@Rango che NON stai ancora evitando, foreachpoiché il codice stesso contiene il foreachloop
GoldBishop,

@GoldBishop certo, il metodo nasconde il ciclo.
Tim Schmelter,

1
Il collegamento è interrotto, ora è disponibile all'indirizzo: codewrecks.com/blog/index.php/2008/08/13/… . C'è anche un commento sul blog che rimanda a stackoverflow.com/questions/200574 . A sua volta, il commento della domanda principale si collega a blogs.msdn.microsoft.com/ericlippert/2009/05/18/… . Forse la risposta sarebbe più semplice riscritta usando MSDN (se lo desideri, potresti comunque accreditare il primo link). Nota a margine: Ruggine ha caratteristiche simili, e, infine, ha dato e aggiunte la funzione equivalente: stackoverflow.com/a/50224248/799204
sourcejedi

15

Uso:

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

Non sono sicuro che si tratti di un uso eccessivo di LINQ o meno, ma ha funzionato per me quando si desidera aggiornare un elemento specifico nell'elenco per una condizione specifica.


7

Non esiste un metodo di estensione incorporato per farlo. Anche se definirne uno è abbastanza semplice. In fondo al post c'è un metodo che ho definito chiamato Iterate. Può essere usato così

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

Iterate Source

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++;
    }
}

Iterate è necessario, cosa c'è di sbagliato in Count, Sum, Avg o altri metodi di estensione esistenti che restituiscono un valore scalare?
AnthonyWJones,

2
questo è abbastanza vicino a quello che voglio, ma un po '.. coinvolto. Il post sul blog che ho pubblicato ha un'implementazione simile ma con meno righe di codice.
lomaxx,

1
IterateHelper sembra eccessivo. Il sovraccarico che non prende un indice finisce per fare molto più lavoro extra (converti la callback in lambda che prende l'indice, mantieni un conteggio che non viene mai usato). Capisco che è un riutilizzo, ma è una soluzione alternativa per il solo utilizzo di un forloop, quindi dovrebbe essere efficiente.
Cameron MacFarland,

2
@Cameron, IterateHelper ha 2 scopi. 1) L'implementazione singola e 2) consente di lanciare ArgumentNullException al momento della chiamata rispetto all'uso. Gli iteratori C # vengono ritardati, avendo l'helper impedisce che si verifichi il comportamento strano di un'eccezione durante l'iterazione.
JaredPar il

2
@JaredPar: tranne che non stai usando un iteratore. Non c'è dichiarazione di rendimento.
Cameron MacFarland,

7

Sebbene tu abbia specificamente richiesto una soluzione LINQ e questa domanda è piuttosto vecchia, inserisco una soluzione non LINQ. Questo perché LINQ (= query integrata nella lingua ) è pensata per essere utilizzata per le query sulle raccolte. Tutti i metodi LINQ non modificano la raccolta sottostante, ma ne restituiscono una nuova (o più precisa un iteratore in una nuova raccolta). Pertanto, qualunque cosa tu faccia, ad es. Con a Select, non influisce sulla collezione sottostante, ne ottieni semplicemente una nuova.

Naturalmente si potrebbe fare con una ForEach(che non è LINQ, tra l'altro, ma un interno List<T>). Ma questo usa letteralmenteforeach comunque, ma con un'espressione lambda. A parte questo, ogni metodo LINQ esegue l'iterazione interna della tua raccolta, ad esempio utilizzando foreacho for, tuttavia, la nasconde semplicemente dal client. Non lo considero più leggibile né mantenibile (pensa a modificare il tuo codice durante il debug di un metodo contenente espressioni lambda).

Detto questo, non dovresti usare LINQ per modificare gli oggetti nella tua collezione. Un modo migliore è la soluzione che hai già fornito nella tua domanda. Con un loop classico puoi facilmente iterare la tua collezione e aggiornarne gli articoli. In effetti tutte quelle soluzioni su cui List.ForEachsi basano non sono nulla di diverso, ma molto più difficile da leggere dal mio punto di vista.

Quindi non dovresti usare LINQ nei casi in cui desideri aggiornare gli elementi della tua raccolta.


3
Off-topic: sono d'accordo, e ci sono moltissimi casi di abuso di LINQ, esempi di persone che richiedono "catene LINQ ad alte prestazioni", per fare ciò che potrebbe essere realizzato con un singolo ciclo, ecc. Sono grato che NON usare LINQ sia troppo radicato in me e in genere non lo uso. Vedo persone che usano le catene LINQ per eseguire una singola azione, senza rendersi conto che praticamente ogni volta che viene utilizzato un comando LINQ, si crea un altro forciclo "sottocoperta". Sento che è zucchero sintetico creare modi meno dettagliati di svolgere compiti semplici, non sostituirsi alla codifica standard.
ForeverZer0

6

Ho provato alcune varianti su questo, e continuo a tornare alla soluzione di questo ragazzo.

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

Ancora una volta, questa è la soluzione di qualcun altro. Ma ho compilato il codice in una piccola libreria e lo uso abbastanza regolarmente.

Ho intenzione di incollare il suo codice qui, per la possibilità che il suo sito (blog) smetta di esistere ad un certo punto in futuro. (Non c'è niente di peggio che vedere un post che dice "Ecco la risposta esatta di cui hai bisogno", fare clic 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; } );

1
È possibile utilizzare un Action<TSource>invece di creare un delegato aggiuntivo. Questo potrebbe non essere stato disponibile al momento della stesura di questo, però.
Frank J

Sì, quella era la vecchia scuola DotNet in quel momento. Buon commento Frank.
granadaCoder

L'URL è morto! (Questo nome di dominio è scaduto) Meno male che ho copiato il codice qui! #patOnShoulder
granadaCoder

1
Controllare il tipo di valore ha senso, ma forse sarebbe meglio usare un vincolo, cioè where T: structper catturarlo al momento della compilazione.
Groo


3

Ho scritto alcuni metodi di estensione per aiutarmi in questo.

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;
        }
    }
}

Lo sto usando in questo modo:

        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

Per riferimento il controllo argomento:

/// <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");
            }
        }

    }
}

2

I miei 2 penny: -

 collection.Count(v => (v.PropertyToUpdate = newValue) == null);

7
Mi piace pensare, ma non è molto chiaro cosa stia facendo il codice
lomaxx,

2

Puoi utilizzare LINQ per convertire la tua raccolta in un array e quindi invocare Array.ForEach ():

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

Ovviamente questo non funzionerà con raccolte di strutture o tipi incorporati come numeri interi o stringhe.


1

Ecco il metodo di estensione che 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;
    }

Perché "gli elementi del tipo di valore non sono supportati dall'aggiornamento" ?? Niente lo interferisce!
abatishchev,

Questo era specifico per il progetto a cui stavo lavorando. Suppongo che nella maggior parte dei casi non importerebbe. Ultimamente l'ho rielaborato e ribattezzato Esegui (...), ho rimosso la cosa del tipo di valore e l'ho cambiata per restituire il vuoto e rilasciare il codice di conteggio.
Bill Forney,

È più o meno quello che List<T>.ForEachfa, ma solo per tutti IEnumerable.
HimBromBeere

Ripensandoci ora, direi che basta usare un ciclo foreach. L'unico vantaggio dell'utilizzo di qualcosa del genere è se si desidera concatenare i metodi insieme e restituire l'enumerabile dalla funzione per continuare la catena dopo aver eseguito l'azione. Altrimenti questa è solo una chiamata di metodo extra per nessun beneficio.
Bill Forney,

0

Suppongo che tu voglia modificare i valori all'interno di una query in modo da poter scrivere una funzione per esso

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; }
}

Ma non certo se questo è ciò che intendi.


Sta andando nella specie della giusta direzione richiederebbe qualcosa per enumerare v, altrimenti non farà nulla.
AnthonyWJones,

0

È possibile utilizzare Magiq , un framework di operazioni batch per LINQ.


-3

Supponiamo di avere dati come di seguito,

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

e se vogliamo modificare l'elenco e sostituire i valori esistenti dell'elenco con valori modificati, quindi prima crea un nuovo elenco vuoto, quindi esegui il ciclo attraverso l'elenco dei dati invocando il metodo di modifica su ciascun elemento dell'elenco,

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;

2
Perché dovresti creare qualcosa che può essere eseguito in O (n) runtime in O (n ^ 2) o peggio? Non sono a conoscenza di come funzionano i dettagli di Linq, ma vedo che questa è almeno una soluzione ^ 2 per un problema n .
Fallenreaper il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.