Zaktualizuj wszystkie obiekty w kolekcji za pomocą LINQ


499

Czy istnieje sposób na wykonanie poniższych czynności przy użyciu LINQ?

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

Aby to wyjaśnić, chcę iterować po każdym obiekcie w kolekcji, a następnie zaktualizować właściwość na każdym obiekcie.

Mój przypadek użycia polega na tym, że mam kilka komentarzy do posta na blogu i chcę iterować każdy komentarz do postu na blogu i ustawić datę i godzinę na blogu na +10 godzin. Mógłbym to zrobić w SQL, ale chcę zachować to w warstwie biznesowej.


14
Interesujące pytanie. Osobiście wolę sposób, w jaki masz to powyżej - o wiele wyraźniejsze, co się dzieje!
noelicus

8
Przyszedłem tutaj, szukając odpowiedzi na to samo pytanie i zdecydowałem, że jest to tak samo łatwe, mniej kodu i łatwiejsze do zrozumienia dla przyszłych programistów, aby zrobić to tak, jak robiłeś to w OP.
Casey Crookston,

4
Dlaczego chcesz to zrobić w LINQ?
Caltor,

13
To pytanie dotyczy niewłaściwej rzeczy, jedyną prawidłową odpowiedzią jest: nie używaj LINQ do modyfikowania źródła danych
Tim Schmelter

Głosuję za zamknięciem tego pytania jako nie na temat, ponieważ prawie wszystkie odpowiedzi na to pytanie są aktywnie szkodliwe dla zrozumienia przez LINK nowych programistów.
Tanveer Badar

Odpowiedzi:


841

Możesz użyć ForEachmetody rozszerzenia, ale jeśli chcesz użyć tylko frameworku, możesz to zrobić

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

Jest ToListto potrzebne do natychmiastowej oceny wyboru ze względu na leniwą ocenę .


6
Głosowałem za tym, ponieważ jest to całkiem fajne rozwiązanie ... jedynym powodem, dla którego podoba mi się metoda rozszerzenia, jest to, że sprawia, że ​​zrozumienie, co się dzieje, jest trochę jaśniejsze ... jednak twoje rozwiązanie jest wciąż całkiem słodkie
lomaxx

9
Jeśli ObservableCollectionpowiedziano o kolekcji , przydatna może być zmiana pozycji zamiast tworzenia nowej listy.
Cameron MacFarland

7
@desaivv tak, to trochę nadużycie w składni, więc Resharper ostrzega cię o tym.
Cameron MacFarland

46
IMHO, jest to o wiele mniej wyraziste niż zwykła pętla foreach. ToList () jest mylące, ponieważ nie jest używane do niczego poza wymuszaniem oceny, która w innym przypadku zostałaby odroczona. Projekcja jest również myląca, ponieważ nie jest używana zgodnie z jej przeznaczeniem; służy raczej do iteracji elementów kolekcji i umożliwia dostęp do właściwości, dzięki czemu można ją aktualizować. Moim zdaniem jedynym pytaniem byłoby, czy pętla foreach mogłaby skorzystać z równoległości za pomocą Parallel.ForEach, ale to inne pytanie.
Philippe

37
Ta odpowiedź jest najgorszą praktyką. Nigdy tego nie rób.
Eric Lippert,

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

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

@CameronMacFarland: Oczywiście, że nie, ponieważ struktury są niezmienne. Ale jeśli naprawdę chcesz, możesz to zrobić:collection.ToList().ForEach(c => { collection[collection.IndexOf(c)] = new <struct type>() { <propertyToSet> = value, <propertyToRetain> = c.Property2Retain }; });
Ε Г И І И О

11
Ma to przewagę nad odpowiedzią Camerona MacFarlanda dotyczącą aktualizacji listy w miejscu, zamiast tworzenia nowej listy.
Simon Tewsi

7
Wow, ta odpowiedź naprawdę nie jest przydatna. Tworzenie nowej kolekcji, aby móc korzystać z pętli
Tim Schmelter,

@ SimonTewsi Ponieważ jest to zbiór obiektów, lista powinna być aktualizowana w każdym miejscu. Kolekcja będzie nowa, ale obiekty w kolekcji będą takie same.
Chris

70

Robię to

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

Myślę, że to najczystszy sposób na zrobienie tego.
wcm

31
Takie podejście z pewnością działa, ale narusza intencję All()metody rozszerzenia, co prowadzi do potencjalnych nieporozumień, gdy ktoś inny czyta kod.
Tom Baxter

To podejście jest lepsze .Używanie wszystkich zamiast korzystania z każdej pętli
UJS

2
Zdecydowanie wolę to od niepotrzebnego wywoływania ToList (), nawet jeśli jest to trochę mylące w kwestii tego, do czego używa All ().
iupchris10,

1
Jeśli używasz takiej kolekcji List<>, ForEach()metoda ta jest znacznie mniej tajemnicza. exForEach(c => { c.needsChange = value; })
Dan Is Fiddling By Firelight

27

I rzeczywiście znaleziono metodę rozszerzenia , które będą robić to, co chcę ładnie

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

4
fajnie :) Lomaxx, może dodaj przykład, aby podglądacze zobaczyli go w „akcji” (boom tish!).
Pure.Krome

2
Jest to jedyne przydatne podejście, jeśli naprawdę chcesz uniknąć foreachpętli (z jakiegokolwiek powodu).
Tim Schmelter,

@Rango, którego wciąż NIE unikasz, foreachponieważ sam kod zawiera foreachpętlę
GoldBishop

@GoldBishop na pewno metoda ukrywa pętlę.
Tim Schmelter,

1
Link jest uszkodzony, jest teraz dostępny pod adresem: codewrecks.com/blog/index.php/2008/08/13/… . Jest też komentarz na blogu, który prowadzi do stackoverflow.com/questions/200574 . Z kolei w komentarzu do najwyższego pytania znajduje się link do blogs.msdn.microsoft.com/ericlippert/2009/05/18/… . Być może odpowiedź byłaby prostsza, ponownie napisana przy użyciu MSDN (nadal możesz przypisać pierwszy link, jeśli chcesz). Sidenote: Rust ma podobne funkcje i ostatecznie poddał się i dodał równoważną funkcję: stackoverflow.com/a/50224248/799204
sourcejedi

14

Posługiwać się:

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

Nie jestem pewien, czy to nadużywa LINQ, czy nie, ale zadziałało to, gdy chcę zaktualizować określone elementy na liście dla określonego warunku.


7

Nie ma wbudowanej metody rozszerzenia, aby to zrobić. Chociaż zdefiniowanie jednego jest dość proste. Na dole postu znajduje się metoda, którą zdefiniowałem, o nazwie Iterate. Można go tak używać

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

Czy konieczne jest wykonywanie iteracji, co jest nie tak z Count, Sum, Avg lub inną istniejącą metodą rozszerzenia, która zwraca wartość skalarną?
AnthonyWJones

2
jest to dość blisko tego, czego chcę, ale trochę… zaangażowane. Post na blogu, który opublikowałem, ma podobną implementację, ale zawiera mniej wierszy kodu.
lomaxx

1
IterateHelper wydaje się przesadą. Przeciążenie, które nie pobiera indeksu, powoduje więcej dodatkowej pracy (konwersja wywołania zwrotnego na lambda, która pobiera indeks, zachowaj liczbę, która nigdy nie jest używana). Rozumiem, że to ponowne użycie, ale i tak jest to obejście po prostu za pomocą forloop, więc powinno być wydajne.
Cameron MacFarland

2
@Cameron, IterateHelper służy 2 celom. 1) Pojedyncza implementacja i 2) pozwala na zgłoszenie wyjątku ArgumentNullException w czasie połączenia a użycie. Iteratory C # są wykonywane z opóźnieniem, a pomocnik zapobiega rzucaniu dziwnego zachowania wyjątku podczas iteracji.
JaredPar

2
@JaredPar: Tyle że nie używasz iteratora. Nie ma deklaracji dochodu.
Cameron MacFarland

7

Chociaż konkretnie poprosiłeś o rozwiązanie LINQ, a to pytanie jest dość stare, zamieszczam rozwiązanie inne niż LINQ. Wynika to z tego, że LINQ (= zapytanie zintegrowane w języku ) jest przeznaczone do użycia w zapytaniach dotyczących kolekcji. Wszystkie metody LINQ nie modyfikują podstawowej kolekcji, po prostu zwracają nową (a dokładniej iterator do nowej kolekcji). Zatem cokolwiek robisz np. Z Selectnie wpływa na kolekcję, po prostu dostajesz nową.

Oczywiście możesz to zrobić za pomocą ForEach(który nie jest LINQ, ale rozszerzenie jest włączone List<T>). Ale i tak dosłownie się to stosuje foreach, ale z wyrażeniem lambda. Poza tym każda metoda LINQ wewnętrznie iteruje Twoją kolekcję, np. Przy użyciu foreachlub for, ale po prostu ukrywa ją przed klientem. Nie uważam tego za bardziej czytelny ani konserwowalny (pomyśl o edycji kodu podczas debugowania metody zawierającej wyrażenia lambda).

Powiedziawszy to, nie powinieneś używać LINQ do modyfikowania przedmiotów w swojej kolekcji. Lepszym sposobem jest rozwiązanie, które już podałeś w swoim pytaniu. Dzięki klasycznej pętli możesz z łatwością iterować swoją kolekcję i aktualizować jej elementy. W rzeczywistości wszystkie te rozwiązania, na których się opierają, List.ForEachnie są niczym innym, ale o wiele trudniejsze do odczytania z mojej perspektywy.

Dlatego nie powinieneś używać LINQ w przypadkach, w których chcesz zaktualizować elementy swojej kolekcji.


3
Poza tematem: Zgadzam się, i jest tak wiele przypadków nadużywania LINQ, przykłady osób żądających „wysokowydajnych łańcuchów LINQ”, aby zrobić to, co można osiągnąć za pomocą pojedynczej pętli itp. Jestem wdzięczny, że NIE używanie LINQ jest zbyt zakorzeniony we mnie i zazwyczaj go nie używam. Widzę ludzi używających łańcuchów LINQ do wykonania pojedynczej akcji, nie zdając sobie sprawy, że prawie za każdym razem, gdy używane jest polecenie LINQ, tworzysz kolejną forpętlę „pod maską”. Wydaje mi się, że syntaktycznym cukrem jest tworzenie mniej szczegółowych sposobów wykonywania prostych zadań, a nie zastępowanie standardowego kodowania.
ForeverZer0,

6

Wypróbowałem kilka wariantów tego i wracam do rozwiązania tego faceta.

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

Ponownie, jest to czyjeś rozwiązanie. Ale skompilowałem kod do małej biblioteki i używam go dość regularnie.

Wkleję tutaj jego kod, aby nie ryzykować, że jego strona (blog) przestanie istnieć w pewnym momencie w przyszłości. (Nie ma nic gorszego niż wyświetlenie postu z napisem „Oto dokładna odpowiedź, której potrzebujesz”, Kliknięcia i Martwego adresu URL).

    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
Możesz użyć Action<TSource>zamiast zamiast tworzenia dodatkowego delegata. Jednak może to nie być dostępne w momencie pisania tego.
Frank J

Tak, to była stara szkoła DotNet w tym momencie. Dobry komentarz Frank.
granadaCoder

Adres URL jest martwy! (Ta nazwa domeny wygasła) Dobrze, że skopiowałem kod tutaj! #patOnShoulder
granadaCoder

1
Sprawdzanie typu wartości ma sens, ale być może lepiej byłoby użyć ograniczenia, tzn. where T: structZłapać to w czasie kompilacji.
Groo


3

Napisałem kilka metod rozszerzenia, które mi w tym pomogą.

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

Używam tego w ten sposób:

        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

Dla porównania: sprawdzenie argumentu:

/// <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

Moje 2 grosze: -

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

7
Podoba mi się myślenie, ale nie jest do końca jasne, co robi kod
lomaxx,

2

Możesz użyć LINQ, aby przekonwertować swoją kolekcję na tablicę, a następnie wywołać Array.ForEach ():

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

Oczywiście nie będzie to działać z kolekcjami struktur lub wbudowanymi typami, takimi jak liczby całkowite lub łańcuchy.


1

Oto metoda rozszerzenia, której używam ...

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

Dlaczego „elementy typu wartości nie są obsługiwane przez aktualizację”? Nic nie przeszkadza!
abatishchev

To było specyficzne dla projektu, nad którym pracowałem. Przypuszczam, że w większości przypadków nie miałoby to znaczenia. Ostatnio przerobiłem to i nadałem mu nazwę Run (...), usunąłem rzecz typu value i zmieniłem ją, aby zwróciła wartość void, i usunąłem kod zliczania.
Bill Forney

To mniej więcej to, co List<T>.ForEachrobi, ale tylko dla wszystkich IEnumerable.
HimBromBeere

Patrząc wstecz na to, powiedziałbym, że wystarczy użyć pętli foreach. Jedyną zaletą korzystania z czegoś takiego jest to, że chcesz połączyć metody razem i zwrócić funkcję zliczania z funkcji, aby kontynuować łańcuch po wykonaniu akcji. W przeciwnym razie jest to tylko dodatkowe wywołanie metody bez korzyści.
Bill Forney,

0

Zakładam, że chcesz zmienić wartości wewnątrz zapytania, aby móc dla niego napisać funkcję

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

Ale nie martw się, jeśli o to ci chodzi.


To idzie w dobrym kierunku, wymagałoby czegoś do wyliczenia v, w przeciwnym razie nic nie zrobi.
AnthonyWJones


-3

Załóżmy, że mamy dane takie jak poniżej,

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

a jeśli chcemy zmodyfikować listę i zastąpić istniejące wartości listy zmodyfikowanymi wartościami, najpierw utwórz nową pustą listę, a następnie przejrzyj listę danych, wywołując metodę modyfikacji dla każdego elementu listy,

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
Dlaczego miałbyś zrobić coś, co może działać w środowisku wykonawczym O (n), wykonać w O (n ^ 2) lub gorzej? IM nieświadomi jak specyfika pracy linq ale widzę to na minimum ^ 2 rozwiązanie dla n problemu.
Fallenreaper
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.