Równoważnik LINQ foreach dla IEnumerable <T>

717

Chciałbym zrobić równoważne z następującymi w LINQ, ale nie wiem, jak to zrobić:

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

Jaka jest prawdziwa składnia?

tags2k
źródło
99
Rozważmy „foreach” vs. „ForEach” Erica Lipperta. Zapewnia to dobre uzasadnienie nieużywania / dostarczania ForEach().
4
@pst: Kiedyś ślepo stosowałem te metody rozszerzenia w swoich projektach. Dzięki za ten artykuł.
Robin Maben,
2
Istnieje MoreLINQ, który ma ForEachrozszerzenie .
Uwe Keim,
2
Chociaż nie jest bardzo seksowny, pasuje do jednej linii bez tworzenia kopii za pośrednictwem ToList -foreach (var i in items) i.Dostuff();
James Westgate
1
Niektóre z tych linków są martwe! Jaki artykuł był tak wysoko oceniany!
Warren

Odpowiedzi:

863

Nie ma rozszerzenia ForEach dla IEnumerable; tylko dla List<T>. Więc możesz to zrobić

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

Możesz też napisać własną metodę rozszerzenia ForEach:

public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach(T item in enumeration)
    {
        action(item);
    }
}
Fredrik Kalseth
źródło
213
Uważaj na ToList (), ponieważ ToList () tworzy kopię sekwencji, która może powodować problemy z wydajnością i pamięcią.
decasteljau
15
Istnieje kilka powodów, dla których „.Foreach ()” będzie lepszy. 1. Wielowątkowość, możliwa przez PLINQ. 2. Wyjątki, możesz zebrać wszystkie wyjątki w pętli i rzucić je od razu; i są to kody hałasu.
Dennis C
12
PLINQ używa ForAll not ForEach, dlatego nie używałbyś ForEach.
user7116 17.07.2010
9
Powinieneś zwrócić IENumerable<T>metodę rozszerzenia. Jak:public static IEnumerable<T> ForAll<T>(this IEnumerable<T> numeration, Action<T> action) { foreach (T item in enumeration) { action(item); } return enumeration; }
Kees C. Bakker,
22
Zauważ, że w pierwszym przypadku programista 1) prawie nie oszczędza pisania i 2) niepotrzebnie przydziela pamięć do przechowywania całej sekwencji jako listy przed jej wyrzuceniem. Pomyśl zanim LINQ
Mark Sowul
364

Fredrik dostarczył poprawkę, ale warto zastanowić się, dlaczego nie ma tego w ramce. Uważam, że pomysł polega na tym, aby operatory zapytań LINQ były wolne od efektów ubocznych, pasując do rozsądnie funkcjonalnego sposobu patrzenia na świat. Oczywiście ForEach jest dokładnie odwrotny - konstrukcja oparta wyłącznie na efektach ubocznych.

Nie oznacza to, że jest to zła rzecz - wystarczy pomyśleć o filozoficznych powodach tej decyzji.

Jon Skeet
źródło
44
A jednak F # ma Seq.iter
Stephen Swensen
6
Jest jednak jedna bardzo przydatna rzecz, której funkcje LINQ zwykle zapewniają, że foreach () tego nie robi - anonimowa funkcja przyjmowana przez funkcje Linq może również przyjmować indeks jako parametr, podczas gdy za pomocą foreach należy odbudować klasyczną dla (int i .. ) konstruować. A języki funkcjonalne muszą zezwalać na iterację, która ma skutki uboczne - w przeciwnym razie nigdy nie można by z nimi zrobić ani jednej pracy :) Chciałbym zauważyć, że typową metodą funkcjonalną byłoby zwrócenie oryginalnego wyliczenia bez zmian.
Walt W.
3
@Walt: Nadal nie jestem pewien, zgadzam się, częściowo dlatego, że typowy podejście prawdziwie funkcjonalne nie używać foreach tak ... byłoby użyć funkcjonalne podejście bardziej jak LINQ, tworząc nową sekwencję.
Jon Skeet
12
@Stephen Swensen: tak, F # ma Seq.iter, ale F # ma również niezmienne struktury danych. przy użyciu Seq.iter na nich, oryginalny Seq nie jest modyfikowany; zwracany jest nowy Seq z efektami ubocznymi.
Anders,
7
@Anders - Seq.iternic nie zwraca, nie mówiąc już o nowym seq z elementami ubocznymi. Tak naprawdę chodzi tylko o skutki uboczne. Być może myślisz Seq.map, ponieważ Seq.iterjest dokładnie równoważny z ForEachmetodą rozszerzenia na IEnumerabe<_>.
Joel Mueller
39

Aktualizacja 17.07.2012: Wygląda na to, że od C # 5.0 zachowanie foreachopisane poniżej zostało zmienione i „ użycie foreachzmiennej iteracyjnej w zagnieżdżonym wyrażeniu lambda nie daje już nieoczekiwanych wynikó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 polega na tym, że jest to niespójne z tym, jak działa odpowiednik „do zrozumienia” w innych językach, i z tym, jak bym się spodziewał, że zadziała (osobista opinia podana tutaj tylko dlatego, że inni wspominali o ich opinia na temat czytelności). Zobacz wszystkie pytania dotyczące „ Dostępu do zmodyfikowanego zamknięcia ”, a także „ Zamknięcia w pętli zmiennej uważanej za szkodliwą ”. Jest to „szkodliwe” tylko ze względu na sposób, w jaki „foreach” jest implementowany w języku C #.

Weź następujące przykłady, używając funkcjonalnie równoważnej metody rozszerzenia do tej z odpowiedzi @Fredrik Kalseth.

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

Przepraszamy za zbyt wymyślony przykład. Używam Observable, ponieważ zrobienie czegoś takiego nie jest zbyt dalekie. Oczywiście są lepsze sposoby na stworzenie tego, co można zaobserwować, staram się tylko wykazać, o co chodzi. Zazwyczaj kod subskrybowany do obserwowalnego jest wykonywany asynchronicznie i potencjalnie w innym wątku. Jeśli użyjesz „foreach”, może to dać bardzo dziwne i potencjalnie niedeterministyczne wyniki.

Następujący test przy użyciu 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()));
}

Błąd kończy się następująco:

Oczekiwany: równoważny <0, 1, 2, 3, 4, 5, 6, 7, 8, 9> Ale był: <9, 9, 9, 9, 9, 9, 9, 9, 9, 9>

[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()));
}
drstevens
źródło
Co mówi ostrzeżenie o ponownym zakupie?
reggaeguitar
1
@reggaeguitar Nie używałem C # od 7 lub 8 lat, ponieważ przed wydaniem C # 5.0, które zmieniło opisane tutaj zachowanie. W tym czasie dał to ostrzeżenie stackoverflow.com/questions/1688465/... . Wątpię jednak, aby nadal to ostrzegało.
drstevens
34

Możesz użyć FirstOrDefault()rozszerzenia, które jest dostępne dla IEnumerable<T>. Po powrocie falsez predykatu zostanie on uruchomiony dla każdego elementu, ale nie będzie miało znaczenia, że ​​tak naprawdę nie znajdzie dopasowania. Pozwoli to uniknąć ToList()kosztów ogólnych.

IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });
Rhames
źródło
11
Też się zgadzam. Może to być sprytny mały hack, ale na pierwszy rzut oka ten kod nie jest jasne, co robi, z pewnością w porównaniu ze standardową pętlą foreach.
Connell,
26
Musiałem zlekceważyć, ponieważ chociaż technicznie poprawne, twoje przyszłe ja i wszyscy twoi następcy będą cię nienawidzić na zawsze, ponieważ zamiast foreach(Item i in GetItems()) { i.DoStuff();}ciebie wziąłeś więcej postaci i sprawił, że było to bardzo mylące
Mark Sowul
14
Ok, ale o wiele bardziej czytelny hack:items.All(i => { i.DoStuff(); return true; }
nawfal
5
Wiem, że jest to dość stary post, ale musiałem go również głosować. Zgadzam się, że to sprytna sztuczka, ale nie do utrzymania. Na pierwszy rzut oka wygląda na to, że robi coś tylko z pierwszym elementem na liście. Może to z łatwością spowodować więcej błędów w przyszłości z powodu niezrozumienia przez programistów, jak to działa i co ma robić.
Lee
4
Jest to programowanie efektów ubocznych i odradza się IMHO.
Anish
21

Wziąłem metodę Fredrika i zmodyfikowałem typ zwrotu.

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

EDYCJA: Jeśli nie było to jasne, każde użycie tej metody musi kończyć się na ToList () lub w jakikolwiek inny sposób, aby zmusić metodę do działania na kompletnym wyliczeniu. W przeciwnym razie akcja nie zostanie 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 to 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 na końcu usuniesz ToList () , test zakończy się niepowodzeniem, ponieważ StringBuilder zawiera pusty ciąg. Jest tak, ponieważ żadna metoda nie wymusiła wyliczenia ForEach.

Dor Rotman
źródło
Twoja alternatywna implementacja ForEachjest interesująca, jednak nie pasuje do zachowania List.ForEach, którego podpis jest public void ForEach(Action<T> action). Nie odpowiada również zachowaniu Observable.ForEachrozszerzenia IObservable<T>, którego podpis jest public static void ForEach<TSource>(this IObservable<TSource> source, Action<TSource> onNext). FWIW, ForEachodpowiednik kolekcji Scali, nawet te leniwe, mają również typ zwracany, który jest równoważny void w języku C #.
drstevens
To najlepsza odpowiedź: odroczone wykonanie + płynne API.
Alexander Danilov,
3
Działa to dokładnie tak samo jak dzwonienie za Selectpomocą ToList. Celem ForEachjest, aby nie musieć dzwonić ToList. Powinien zostać wykonany natychmiast.
t3chb0t
3
Jaka byłaby korzyść z posiadania tego „ForEach”, którego wykonanie jest odroczone, w porównaniu z wykonaniem natychmiastowym? Odroczenie (co jest niewygodne w sposobie użycia ForEach) nie poprawia faktu, że ForEach wykonuje użyteczną pracę tylko wtedy, gdy działanie ma skutki uboczne [zwykła skarga skierowana przeciwko takiemu operatorowi Linq]. Tak, jak robi się to zmienić pomocy? Ponadto dodana funkcja „ToList ()” w celu wymuszenia jej wykonania nie ma żadnego pożytecznego celu. To wypadek, który czeka. Punktem „ForEach” są jego skutki uboczne. Jeśli CHCESZ zwrócić zwrócone wyliczenie, SELECT ma większy sens.
ToolmakerSteve
20

Trzymaj swoje efekty uboczne poza moim IEnumerable

Chciałbym zrobić równoważne z następującymi w LINQ, ale nie wiem, jak to zrobić:

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

Czy naprawdę chcesz „zrobić coś” dla każdego elementu w IEnumerable? To foreachjest najlepszy wybór. Ludzie nie są zaskoczeni, gdy pojawiają się tutaj skutki uboczne.

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

Założę się, że nie chcesz efektu ubocznego

Jednak z mojego doświadczenia wynika, że ​​działania niepożądane zwykle nie są wymagane. Najczęściej jest czekające na odkrycie proste zapytanie LINQ wraz z odpowiedzią StackOverflow.com autorstwa Jona Skeeta, Erica Lipperta lub Marca Gravella wyjaśniającą, jak zrobić to, co chcesz!

Kilka przykładów

Jeśli faktycznie agregujesz (gromadzisz) jakąś wartość, powinieneś rozważyć Aggregatemetodę rozszerzenia.

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

Być może chcesz utworzyć nowy IEnumerablez istniejących wartości.

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

A może chcesz utworzyć tabelę przeglądową:

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

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

cdiggins
źródło
7
Ach, więc Select to Map, a Aggregate to Reduce.
Darrel Lee,
17

Jeśli chcesz działać jak rzuty wyliczeniowe, powinieneś dać każdy przedmiot.

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;
        }
    }
}
regisbsb
źródło
To nie jest tak naprawdę ForEach po zwróceniu przedmiotu, to raczej kran.
EluciusFTW
10

Istnieje eksperymentalna wersja Microsoft Interaktywnych rozszerzeń do LINQ (także na NuGet , zobacz profil RxTeams, aby uzyskać więcej linków). Film na kanale 9 dobrze to wyjaśnia.

Dokumenty są dostarczane tylko w formacie XML. Uruchomiłem tę dokumentację w Sandcastle , aby była w bardziej czytelnym formacie. Rozpakuj archiwum dokumentów i poszukaj pliku index.html .

Wśród wielu innych korzyści zapewnia oczekiwaną implementację ForEach. Pozwala napisać taki kod:

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

numbers.ForEach(x => Console.WriteLine(x*x));
John Wigger
źródło
2
Działający link do Interaktywnych rozszerzeń: microsoft.com/download/en/details.aspx?id=27203
Maciek Świszczowski
8

Zgodnie z PLINQ (dostępne od .Net 4.0), możesz zrobić

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

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

Wolf5
źródło
dopóki twoje działanie jest bezpieczne dla wątków, wszystko jest w porządku. ale co się stanie, jeśli wykonasz akcję, która nie jest bezpieczna dla wątków? może to spowodować całkiem chaos ...
shirbr510
2
Prawdziwe. Nie jest to bezpieczne dla wątków, z pewnej puli wątków będą używane różne wątki ... I muszę zmienić swoją odpowiedź. Gdy próbuję teraz, nie ma ForEach (). Musiałem mieć kod zawierający rozszerzenie ForEach, gdy się nad tym zastanawiałem.
Wolf5
6

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

Ta różnica pojęciowa jest dość widoczna, gdy się ją uwzględni.

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

Nie będzie to wykonywane, dopóki nie wykonasz „count”, „ToList ()” lub czegoś w tym stylu. Wyraźnie nie jest to, co jest wyrażone.

Powinieneś używać IEnumerable rozszerzeń do konfigurowania łańcuchów iteracji, definiowania treści według ich odpowiednich źródeł i warunków. Drzewa ekspresji są potężne i wydajne, ale powinieneś nauczyć się doceniać ich naturę. I to nie tylko do programowania wokół nich, aby zapisać kilka znaków zastępujących leniwe oceny.

Tormod
źródło
5

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

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

Krótkie i proste (st).

Nenad
źródło
Możesz upuścić całą pierwszą linię i użyć GetItems bezpośrednio w foreach
tymtam
3
Oczywiście. Jest tak napisane dla jasności. Aby było jasne, która GetItems()metoda zwraca.
Nenad
4
Teraz, kiedy czytam mój komentarz, widzę, że mówię ci coś, czego nie wiesz. Nie miałem tego na myśli. Chodziło mi o to, że foreach (var item in GetItems()) item.DoStuff();to tylko piękno.
tymtam
4

Teraz mamy opcję ...

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

Oczywiście otwiera to zupełnie nową puszkę owsików.

ps (Przepraszam za czcionki, tak zdecydował system)

Paulustrious
źródło
4

Jak już wskazują liczne odpowiedzi, możesz z łatwością dodać taką metodę rozszerzenia samodzielnie. Jeśli jednak nie chcesz tego robić, chociaż nie znam czegoś takiego w BCL, nadal istnieje opcja w Systemprzestrzeni nazw, jeśli masz już odniesienie do Reactive Extension (a jeśli nie masz , powinieneś mieć):

using System.Reactive.Linq;

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

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

Mark Seemann
źródło
Wygląda na to, że ten pakiet jest już nieaktualny
reggaeguitar
3

ForEach może być również Przykuty , wystarczy umieścić z powrotem do pileline po akcji. zachowaj płynność


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

Marzą wersja realizacji.

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
}

Edit: Lazy wersja używa zwrotu plastyczności, jak ten .

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

Wersja Lazy POTRZEBUJE się zmaterializować, na przykład ToList (), w przeciwnym razie nic się nie stanie. patrz poniżej świetne komentarze ToolmakerSteve.

IQueryable<Product> query = Products.Where(...);
query.ForEachLazy(t => t.Price = t.Price + 1.00)
    .ToList(); //without this line, below SubmitChanges() does nothing.
SubmitChanges();

W mojej bibliotece przechowuję zarówno ForEach (), jak i ForEachLazy ().

Rm558
źródło
2
Testowałeś to? Istnieje kilka kontekstów, w których ta metoda zachowuje się przeciwnie intuicyjnie. Lepiej byłobyforeach(T item in enu) {action(item); yield return item;}
Taemyr
2
Spowoduje to wiele wyliczeń
regisbsb
Ciekawy. Zastanawiasz się, kiedy chciałbym zrobić powyższe, zamiast sekwencji 3 działań foreach (T item in enu) { action1(item); action2(item); action3(item); }? Pojedyncza, wyraźna pętla. Wydaje mi się, że ważne było wykonanie wszystkich akcji PRZED rozpoczęciem wykonywania akcji 2.
ToolmakerSteve
@ Rm558 - Twoje podejście przy użyciu „zwrotu z zysku”. Pro: wylicza oryginalną sekwencję tylko raz, więc działa poprawnie z szerszym zakresem wyliczeń. Przeciw: jeśli na końcu zapomnisz „.ToArray ()”, nic się nie stanie. Chociaż awaria będzie oczywista, nie zostanie przeoczona na długo, więc nie będzie to poważny koszt. Nie czuje się dla mnie „czysty”, ale jest właściwym kompromisem, jeśli idzie się tą drogą (operator ForEach).
ToolmakerSteve
1
Ten kod nie jest bezpieczny z transakcyjnego punktu widzenia. Co jeśli się nie uda ProcessShipping()? E-mail do kupującego, obciążenie jego karty kredytowej, ale nigdy nie wysyłasz mu swoich rzeczy? Z pewnością prosi o problemy.
zmechanic 13.04.17
2

Ta abstrakcja „funkcjonalnego podejścia” przecieka przez długi czas. Nic na poziomie języka nie zapobiega skutkom ubocznym. Tak długo, jak możesz wywołać swoją lambda / delegata dla każdego elementu w kontenerze - otrzymasz zachowanie „ForEach”.

Oto na przykład jeden ze sposobów połączenia srcDictionary w destDictionary (jeśli klucz już istnieje - zastępuje)

jest to hack i nie należy go używać w żadnym kodzie produkcyjnym.

var b = srcDictionary.Select(
                             x=>
                                {
                                  destDictionary[x.Key] = x.Value;
                                  return true;
                                }
                             ).Count();
Zar Shardan
źródło
6
Jeśli musisz dodać to wyłączenie odpowiedzialności, prawdopodobnie nie powinieneś go publikować. Tylko moja opinia.
Andrew Grothe
2
Jak do diabła jest to lepsze niż foreach(var tuple in srcDictionary) { destDictionary[tuple.Key] = tuple.Value; }? Jeśli nie w kodzie produkcyjnym, to gdzie i dlaczego warto z tego korzystać?
Mark Sowul
3
To po prostu, aby pokazać, że jest to w zasadzie możliwe. Zdarza się również, aby robić to, o co prosiła OP, i wyraźnie wskazałem, że nie powinien być stosowany w produkcji, nadal jest ciekawy i ma wartość edukacyjną.
Zar Shardan
2

Zainspirowany przez Jona Skeeta, rozszerzyłem jego rozwiązanie o:

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);
    }
Scott Nimrod
źródło
1

Nie zgadzam się z opinią, że metody rozszerzenia linków powinny być wolne od skutków ubocznych (nie tylko dlatego, że nie są, żaden delegat może wywoływać skutki uboczne).

Rozważ następujące:

   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, to tak naprawdę rodzaj 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żego konstruktu przełączającego w celu zdekodowania wartości definiującej akcję i tłumaczenia do odpowiedniej metody.

caddzooks
źródło
9
Istnieje różnica między tym, czy metoda rozszerzenia MOŻE mieć skutki uboczne, a czy POWINNA mieć. Wskazujesz tylko, że może to mieć, podczas gdy mogą istnieć bardzo dobre powody, aby zaproponować, aby nie miały skutków ubocznych. W programowaniu funkcjonalnym wszystkie funkcje są wolne od skutków ubocznych, a podczas korzystania z funkcjonalnych konstrukcji programowych możesz założyć, że są.
Dave Van den Eynde
1

Aby zachować biegłość, można zastosować taką sztuczkę:

GetItems()
    .Select(i => new Action(i.DoStuf)))
    .Aggregate((a, b) => a + b)
    .Invoke();
Alex
źródło
0

W przypadku VB.NET należy użyć:

listVariable.ForEach(Sub(i) i.Property = "Value")
Israel Margulies
źródło
UWAGA: Kompiluje się tylko, jeśli masz Listlub IList. Ogólnie IEnumerable, napisz VB odpowiednika metody rozszerzenia ForEach przyjętej odpowiedzi .
ToolmakerSteve
-1

Jeszcze inny ForEachprzykład

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;
}
Neil Martin
źródło
4
Co za strata pamięci. To wywołuje ToList, aby dodać do listy. Dlaczego to nie zwraca adresów. Wybierz (a => MapToDomain (a))?
Mark Sowul