LINQ odpowiednik foreach dla IEnumerable

Chciałbym zrobić odpowiednik poniższego w LINQ, ale nie wiem jak:

IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());

Jaka jest prawdziwa składnia?

Author: abatishchev, 2008-10-14

20 answers

Nie ma rozszerzenia ForEach dla IEnumerable; tylko dla List<T>. So you could do

items.ToList().ForEach(i => i.DoStuff());

Alternatywnie, napisz własną metodę rozszerzenia ForEach:

public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach(T item in enumeration)
    {
        action(item);
    }
}
 744
Author: Fredrik Kalseth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-29 08:37:48

Fredrik dostarczył poprawkę, ale może warto zastanowić się, dlaczego nie jest to w frameworku na początek. Uważam, że idea jest taka, że operatory zapytań LINQ powinny być wolne od skutków ubocznych, pasujące do w miarę funkcjonalnego sposobu patrzenia na świat. Oczywiście ForEach jest dokładnie odwrotnie-a czysto konstrukcja oparta na efektach ubocznych.

To nie znaczy, że jest to zła rzecz - tylko myślenie o filozoficznych powodach tej decyzji.

 330
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-14 10:10:51

Update 17/07/2012: najwyraźniej od C# 5.0 zachowanie foreach opisane poniżej zostało zmienione i "użycie zmiennej iteracji foreach w zagnieżdżonym wyrażeniu lambda nie daje już nieoczekiwanych rezultatów. " ta odpowiedź Nie dotyczy C # ≥ 5.0.

@ John Skeet i wszyscy, którzy preferują słowo kluczowe foreach.

Problem z "foreach" w C# przed 5.0, jest, że jest niezgodny z tym, jak odpowiednik " dla zrozumienia" działa w innych językach i z tym, jak bym się tego spodziewał (osobista opinia podana tutaj tylko dlatego, że inni wymienili swoją opinię na temat czytelności). Zobacz wszystkie pytania dotyczące " dostęp do zmodyfikowanego zamknięcia " jak również "Zamknięcie zmiennej pętli uznanej za szkodliwą". Jest to "szkodliwe" tylko ze względu na sposób implementacji "foreach" w C#.

Weź następujące przykłady używając funkcjonalnie równoważnej metody rozszerzenia do tej w @ Fredrik Kalseth ' s answer.

public static class Enumerables
{
    public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
    {
        foreach (T item in @this)
        {
            action(item);
        }
    }
}

Przepraszam za zbyt wymyślony przykład. Używam tylko Observable, ponieważ nie jest to całkowicie naciągane, aby zrobić coś takiego. Oczywiście są lepsze sposoby na stworzenie tego obserwowalnego, staram się tylko wykazać punkt. Zazwyczaj kod subskrybowany do obserwowalnego jest wykonywany asynchronicznie i potencjalnie w innym wątku. Jeśli użyjesz "foreach", może to spowodować bardzo dziwne i potencjalnie niedeterministyczne wyniki.

Następujący test z wykorzystaniem metody rozszerzenia "ForEach" przechodzi pomyślnie:

[Test]
public void ForEachExtensionWin()
{
    //Yes, I know there is an Observable.Range.
    var values = Enumerable.Range(0, 10);

    var observable = Observable.Create<Func<int>>(source =>
                            {
                                values.ForEach(value => 
                                    source.OnNext(() => value));

                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Win
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}

Poniższy błąd nie powiódł się:

oczekiwano: równowartość Ale był:

[Test]
public void ForEachKeywordFail()
{
    //Yes, I know there is an Observable.Range.
    var values = Enumerable.Range(0, 10);

    var observable = Observable.Create<Func<int>>(source =>
                            {
                                foreach (var value in values)
                                {
                                    //If you have resharper, notice the warning
                                    source.OnNext(() => value);
                                }
                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Fail
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}
 33
Author: drstevens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:29

Możesz użyć rozszerzenia FirstOrDefault(), które jest dostępne dla IEnumerable<T>. Zwracając false z predykatu, będzie on uruchamiany dla każdego elementu, ale nie będzie przejmował się tym, że nie znajdzie dopasowania. To pozwoli uniknąć ToList() napowietrznych.

IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });
 31
Author: Rhames,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-12 10:39:40

Zastosowałem metodę Fredrika i zmodyfikowałem Typ powrotu.

W ten sposób metoda obsługuje odroczone wykonanie podobnie jak inne metody LINQ.

EDIT: jeśli to nie było jasne, każde użycie tej metody musi kończyć się tolist() lub w inny sposób zmusić metodę do pracy na pełnym wyliczeniu. W przeciwnym razie akcja nie zostałaby wykonana!

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach (T item in enumeration)
    {
        action(item);
        yield return item;
    }
}

A oto test, który pomoże go zobaczyć:

[Test]
public void TestDefferedExecutionOfIEnumerableForEach()
{
    IEnumerable<char> enumerable = new[] {'a', 'b', 'c'};

    var sb = new StringBuilder();

    enumerable
        .ForEach(c => sb.Append("1"))
        .ForEach(c => sb.Append("2"))
        .ToList();

    Assert.That(sb.ToString(), Is.EqualTo("121212"));
}

Jeśli usuniesz ToList () w na końcu zobaczysz, że test się nie powiedzie, ponieważ StringBuilder zawiera pusty ciąg znaków. Dzieje się tak, ponieważ żadna metoda nie zmusiła ForEach do wyliczenia.

 19
Author: Dor Rotman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-03 16:39:27

Trzymaj swoje skutki uboczne z dala od mojej liczby

Chciałbym zrobić odpowiednik poniższego w LINQ, ale nie wiem jak:

Jak zauważyli inni tutaj i za granicą metody LINQ i IEnumerable powinny być wolne od skutków ubocznych.

Czy naprawdę chcesz "zrobić coś" z każdym elementem w IEnumerable? Wtedy foreach jest najlepszym wyborem. Ludzie nie są zaskoczeni, gdy efekty uboczne zdarzają się tutaj.

foreach (var i in items) i.DoStuff();

I bet you don ' t want a side-effect

Jednak z mojego doświadczenia skutki uboczne zazwyczaj nie są wymagane. Częściej niż nie jest proste zapytanie LINQ czeka na odkrycie wraz z StackOverflow.com odpowiedź albo Jon Skeet, Eric Lippert, lub Marc Gravell wyjaśniając, jak zrobić to, co chcesz!

Niektóre przykłady

Jeśli rzeczywiście agregujesz (kumulujesz) jakąś wartość, powinieneś rozważyć rozszerzenie Aggregate metoda.

items.Aggregate(initial, (acc, x) => ComputeAccumulatedValue(acc, x));

Być może chcesz utworzyć nową IEnumerable z istniejących wartości.

items.Select(x => Transform(x));

A może chcesz utworzyć tabelę wyszukiwania:

items.ToLookup(x, x => GetTheKey(x))

Lista (kalambur nie do końca zamierzony) możliwości jest długa i długa.

 13
Author: cdiggins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:59

Istnieje eksperymentalne wydanie przez Microsoft interaktywnych rozszerzeń do LINQ (również na NuGet , Zobacz profil RxTeams aby uzyskać więcej linków). Film Channel 9 dobrze to wyjaśnia.

Jego dokumenty są dostarczane tylko w formacie XML. Uruchomiłem tę dokumentację W Sandcastle , aby umożliwić jej bardziej czytelny format. Rozpakuj archiwum dokumentów i poszukaj indeksu .html .

Wśród wielu innych gadżetów, dostarcza oczekiwanych ForEach implementation. Pozwala na pisanie kodu w ten sposób:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };

numbers.ForEach(x => Console.WriteLine(x*x));
 8
Author: John Wigger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-06 04:27:51

Jeśli chcesz działać jako rolki wyliczeniowe, powinieneś podać każdy element.

public static class EnumerableExtensions
{
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
    {
        foreach (var item in enumeration)
        {
            action(item);
            yield return item;
        }
    }
}
 7
Author: regisbsb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-31 16:04:53

Zgodnie z PLINQ (dostępnym od. Net 4.0), można wykonać

IEnumerable<T>.AsParallel().ForAll() 

Aby zrobić równoległą pętlę foreach na IEnumerable.

 7
Author: Wolf5,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-17 14:10:26

Celem ForEach jest wywoływanie skutków ubocznych. IEnumerable służy do leniwego wyliczania zbioru.

Ta różnica pojęciowa jest całkiem widoczna, gdy się ją weźmie pod uwagę.

SomeEnumerable.ForEach(item=>DataStore.Synchronize(item));

To nie będzie wykonywane, dopóki nie zrobisz "count " lub" ToList () " lub coś na nim. To wyraźnie nie jest to, co jest wyrażone.

Powinieneś używać rozszerzeń IEnumerable do konfigurowania łańcuchów iteracji, definiowania treści według ich odpowiednich źródeł i warunków. Drzewa wyrazu to potężny i wydajny, ale powinieneś nauczyć się doceniać ich naturę. I nie tylko do programowania wokół nich, aby zapisać kilka znaków nadpisujących leniwą ocenę.

 5
Author: Tormod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-01 08:46:38

Jak wiele odpowiedzi już wskazuje, możesz łatwo dodać taką metodę rozszerzenia samodzielnie. Jednak, jeśli nie chcesz tego robić, chociaż nie jestem świadomy czegoś takiego w BCL, nadal istnieje opcja w przestrzeni nazw System, Jeśli masz już odniesienie do Reactive Extension (a jeśli nie, powinieneś mieć):

using System.Reactive.Linq;

items.ToObservable().Subscribe(i => i.DoStuff());

Chociaż nazwy metod są nieco inne, efekt końcowy jest dokładnie tym, czego szukasz.

 4
Author: Mark Seemann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-05 20:27:29

Wiele osób o tym wspominało, ale musiałem to zapisać. Czy to nie jest najbardziej jasne/czytelne?

IEnumerable<Item> items = GetItems();
foreach (var item in items) item.DoStuff();

Krótkie i proste (st).

 3
Author: Nenad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-08 22:47:32

Teraz mamy możliwość...

        ParallelOptions parallelOptions = new ParallelOptions();
        parallelOptions.MaxDegreeOfParallelism = 4;
#if DEBUG
        parallelOptions.MaxDegreeOfParallelism = 1;
#endif
        Parallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));

Oczywiście, to otwiera zupełnie nową puszkę threadworms.

Ps (przepraszam za czcionki, tak zdecydował system)

 2
Author: Paulustrious,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-05 17:47:48

Zainspirowany Jonem Skeetem, rozszerzyłem jego rozwiązanie o następujące:

Metoda Rozszerzenia:

public static void Execute<TSource, TKey>(this IEnumerable<TSource> source, Action<TKey> applyBehavior, Func<TSource, TKey> keySelector)
{
    foreach (var item in source)
    {
        var target = keySelector(item);
        applyBehavior(target);
    }
}

Klient:

var jobs = new List<Job>() 
    { 
        new Job { Id = "XAML Developer" }, 
        new Job { Id = "Assassin" }, 
        new Job { Id = "Narco Trafficker" }
    };

jobs.Execute(ApplyFilter, j => j.Id);

. . .

    public void ApplyFilter(string filterId)
    {
        Debug.WriteLine(filterId);
    }
 1
Author: Scott Nimrod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-11 00:45:30

ForEach może być równieżprzykuty łańcuchem , po prostu odłożyć z powrotem do pileliny po akcji. pozostań płynny


Employees.ForEach(e=>e.Act_A)
         .ForEach(e=>e.Act_B)
         .ForEach(e=>e.Act_C);

Orders  //just for demo
    .ForEach(o=> o.EmailBuyer() )
    .ForEach(o=> o.ProcessBilling() )
    .ForEach(o=> o.ProcessShipping());


//conditional
Employees
    .ForEach(e=> {  if(e.Salary<1000) e.Raise(0.10);})
    .ForEach(e=> {  if(e.Age   >70  ) e.Retire();});

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enu, Action<T> action)
{
    foreach (T item in enu) action(item);
    return enu; // make action Chainable/Fluent
}

Edit2 powyższy kod działa, ale lepsza wersja używa tego .

Edit Poniżej był błędnym przykładem, wskazanym przez Taemyra. Wielkie dzięki.

Employees.ForEach(e=>e.Salary = e.Salary * 2)
         .Where (e=> e.Salary > 10000)
         .Average(e=> e.Salary);

 1
Author: Rm558,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:44

Nie zgadzam się z poglądem, że metody rozszerzenia linków powinny być wolne od efektów ubocznych (nie tylko dlatego, że nie są, każdy delegat może wykonywać efekty uboczne).

Rozważ co następuje:

   public class Element {}

   public Enum ProcessType
   {
      This = 0, That = 1, SomethingElse = 2
   }

   public class Class1
   {
      private Dictionary<ProcessType, Action<Element>> actions = 
         new Dictionary<ProcessType,Action<Element>>();

      public Class1()
      {
         actions.Add( ProcessType.This, DoThis );
         actions.Add( ProcessType.That, DoThat );
         actions.Add( ProcessType.SomethingElse, DoSomethingElse );
      }

      // Element actions:

      // This example defines 3 distict actions
      // that can be applied to individual elements,
      // But for the sake of the argument, make
      // no assumption about how many distict
      // actions there may, and that there could
      // possibly be many more.

      public void DoThis( Element element )
      {
         // Do something to element
      }

      public void DoThat( Element element )
      {
         // Do something to element
      }

      public void DoSomethingElse( Element element )
      {
         // Do something to element
      }

      public void Apply( ProcessType processType, IEnumerable<Element> elements )
      {
         Action<Element> action = null;
         if( ! actions.TryGetValue( processType, out action ) )
            throw new ArgumentException("processType");
         foreach( element in elements ) 
            action(element);
      }
   }

To, co pokazuje przykład, jest tak naprawdę rodzajem późnego wiązania, które pozwala wywołać jedną z wielu możliwych akcji mających skutki uboczne na sekwencji elementów, bez konieczności pisania dużej konstrukcji przełącznika, aby dekodować wartość, która definiuje akcję i przekładać ją na jego odpowiednia metoda.

 0
Author: 2 revscaddzooks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-10 03:49:28

Ta abstrakcja "podejścia funkcjonalnego" przecieka bardzo szybko. Nic na poziomie języka nie zapobiega skutkom ubocznym. Tak długo, jak możesz wywołać lambda / delegate dla każdego elementu w kontenerze-otrzymasz zachowanie "ForEach".

Tutaj na przykład można połączyć srcDictionary z destDictionary (jeśli klucz już istnieje - nadpisuje)

Jest to hack i nie powinien być używany w żadnym kodzie produkcyjnym.

var b = srcDictionary.Select(
                             x=>
                                {
                                  destDictionary[x.Key] = x.Value;
                                  return true;
                                }
                             ).Count();
 0
Author: Zar Shardan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-03 00:47:40

Dla VB.NET należy użyć:

listVariable.ForEach(Sub(i) i.Property = "Value")
 0
Author: Israel Margulies,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-14 19:41:58

Yet another ForEach Example

public static IList<AddressEntry> MapToDomain(IList<AddressModel> addresses)
{
    var workingAddresses = new List<AddressEntry>();

    addresses.Select(a => a).ToList().ForEach(a => workingAddresses.Add(AddressModelMapper.MapToDomain(a)));

    return workingAddresses;
}
 -1
Author: neil martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-17 08:20:22

Jeśli robisz to np. dlatego, że potrzebujesz indeksu w iteracji, zawsze możesz użyć konstrukcji Where:

linqObject.Where((obj, index) => {
  DoWork(obj, index);
  return true;
}).ToArray(); //MUST CALL ToArray() or ToList() or something to execute the lazy query, or the loop won't actually execute

Ma to dodatkową zaletę, że oryginalna tablica jest zwracana "bez zmian" (obiekty, do których odwołuje się lista, są takie same, choć mogą nie mieć tych samych danych), co jest często pożądane w funkcyjnych / łańcuchowych metodach programowania, takich jak LINQ.

 -2
Author: Walt W,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-01 17:26:18