it-swarm-tr.com

IEnumerable'da neden ForEach eklentisi yöntemi yok?

Eksik Zip işlevini soran başka bir sorudan ilham alındı:

ForEach sınıfında neden Enumerable extension yöntemi yok? Ya da herhangi bir yerde? ForEach yöntemi alan tek sınıf List<>. Eksik olmasının bir nedeni var mı (performans)?

355

Zaten çoğu zaman işi yapan dilde bulunan bir foreach ifadesi zaten var.

Aşağıdakileri görmekten nefret ediyorum:

list.ForEach( item =>
{
    item.DoSomething();
} );

Onun yerine:

foreach(Item item in list)
{
     item.DoSomething();
}

İkincisi daha net ve okunması kolaydır çoğu durumda, yazması biraz daha uzun olsa da.

Ancak, bu konudaki tutumumu değiştirdiğimi itiraf etmeliyim; Bir ForEach () uzantısı yöntemi gerçekten de bazı durumlarda yararlı olacaktır.

İfade ve yöntem arasındaki ana farklar şunlardır:

  • Tip denetimi: foreach çalışma zamanında, ForEach () derleme zamanında (Big Plus!)
  • Bir temsilciyi çağırma sözdizimi gerçekten çok daha basittir: objects.ForEach (DoSomething);
  • ForEach () zincirlenebilir: Her ne kadar böyle bir özelliğin kötülük/kullanışlılığı tartışmaya açık olsa da.

Bunların hepsi burada birçok insan tarafından yapılmış harika noktalar ve insanların neden fonksiyonunu kaybettiğini görebiliyorum. Microsoft'un bir sonraki çerçeve yinelemesinde standart bir ForEach yöntemi eklemeyi umursamıyorum.

187
Coincoin

LINQ'dan önce ForEach metodu eklendi. ForEach uzantısı eklerseniz, uzantı yöntemleri kısıtlamaları nedeniyle liste örnekleri için hiçbir zaman çağrılmaz. Bunun eklenmemesinin sebebinin mevcut olanla etkileşime girmemesi olduğunu düşünüyorum.

Ancak, bu küçük Nice işlevini gerçekten çok özlüyorsanız, kendi sürümünüzü kullanıma sunabilirsiniz.

public static void ForEach<T>(
    this IEnumerable<T> source,
    Action<T> action)
{
    foreach (T element in source) 
        action(element);
}
72
aku

Bu uzantı yöntemini yazabilirsiniz:

// Possibly call this "Do"
IEnumerable<T> Apply<T> (this IEnumerable<T> source, Action<T> action)
{
    foreach (var e in source)
    {
        action(e);
        yield return e;
    }
}

Proc

Zincirlemeye izin verir:

MySequence
    .Apply(...)
    .Apply(...)
    .Apply(...);

Dezavantajları

Siz yinelemeyi zorlayacak bir şey yapana kadar hiçbir şey yapmaz. Bu nedenle, .ForEach() olarak adlandırılmamalıdır. Sonunda .ToList() yazabilir ya da bu uzantı yöntemini de yazabilirsiniz:

// possibly call this "Realize"
IEnumerable<T> Done<T> (this IEnumerable<T> source)
{
    foreach (var e in source)
    {
        // do nothing
        ;
    }

    return source;
}

Bu, C # kütüphanelerinin nakliyesinden ayrılmak için çok önemli olabilir; Uzatma yöntemlerine aşina olmayan okuyucular kodunuzu ne yapacağınızı bilemez.

50
Jay Bazuzi

Buradaki tartışma cevabı verir:

Aslında, tanık olduğum özel tartışma aslında işlevsel saflığa da bağlıydı. Bir ifadede, yan etkileri olmadığı hakkında yapılan varsayımlar sıklıkla vardır. ForEach'a sahip olmak, sadece yanlarına koymaktan ziyade, özellikle yan etkileri davet ediyor. - Keith Farmer (Ortak)

Temel olarak, uzatma yöntemlerini işlevsel olarak "saf" tutmaya karar verildi. Bir ForEach, niyet olmayan Numaralandırılabilir uzatma yöntemlerini kullanırken yan etkileri teşvik eder.

33
mancaus

Çoğu durumda yerleşik foreach yapısını kullanmanın daha iyi olduğu konusunda hemfikir olsam da, ForEach <> uzantısında bu varyasyonun, dizini normal foreach'da kendimden geçirmenin biraz daha iyi olduğunu düşünüyorum:

public static int ForEach<T>(this IEnumerable<T> list, Action<int, T> action)
{
    if (action == null) throw new ArgumentNullException("action");

    var index = 0;

    foreach (var elem in list)
        action(index++, elem);

    return index;
}
var people = new[] { "Moe", "Curly", "Larry" };
people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p));

Sana veririm:

Person #0 is Moe
Person #1 is Curly
Person #2 is Larry
16
Chris Zwiryk

Çözümlerden biri, .ToList().ForEach(x => ...) yazmaktır.

lehte

Anlaması kolay - okuyucunun yalnızca C # ile ne gönderdiğini bilmesi gerekiyor, ek uzantı yöntemlerini değil.

Sözdizimsel gürültü çok hafiftir (sadece biraz yabancı kod ekler).

Yerel bir .ForEach() zaten tüm koleksiyonu gerçekleştirmek zorunda kalacağından, genellikle fazladan belleğe mal olmaz.

cons

İşlem sırası ideal değil. Bir elementi fark etmeyi tercih ederim, sonra harekete geçin, sonra tekrarlayın. Bu kod ilk önce tüm elemanları fark eder, daha sonra sırasıyla her birine etki eder.

Listenin farkına varmak bir istisna atarsa, hiçbir zaman tek bir öğe üzerinde harekete geçemezsiniz.

Numaralandırma sonsuz ise (doğal sayılar gibi), şansınız kalmaz.

14
Jay Bazuzi

Her zaman merak ettim, bu yüzden bunu hep yanımda taşıyorum.

public static void ForEach<T>(this IEnumerable<T> col, Action<T> action)
{
    if (action == null)
    {
        throw new ArgumentNullException("action");
    }
    foreach (var item in col)
    {
        action(item);
    }
}

Güzel küçük uzatma yöntemi.

13
Aaron Powell

Bu nedenle, bir ForEach uzatma yönteminin uygun olmadığı, çünkü LINQ uzatma yöntemleri gibi bir değer getirmediği hakkında çok yorum yapıldı. Bu gerçek bir ifade olsa da, tamamen doğru değildir.

LINQ eklentisi yöntemleri, hepsi bir değer döndürür, böylece birlikte zincirlenebilirler:

collection.Where(i => i.Name = "hello").Select(i => i.FullName);

Bununla birlikte, LINQ'nun uzatma yöntemleri kullanılarak gerçekleştirilmesi, uzatma yöntemlerinin must aynı şekilde kullanılması ve bir değer döndürmesi anlamına gelmez. Bir değer döndürmeyen ortak işlevselliği ortaya çıkarmak için bir uzatma yöntemi yazmak, mükemmel geçerli bir kullanımdır.

ForEach hakkındaki özel tartışma, uzatma yöntemleriyle ilgili kısıtlamalara dayanarak (yani bir uzatma yönteminin asla ile aynı imza ile devralınan bir yöntemi geçersiz kılacağı) olabilir. Özel uzantı yönteminin IEnumerable<T> uygulayan, List<T> hariç tüm sınıflarda mevcut olduğu bir durum. Bu, uzatma yönteminin miras devralma yönteminin mi çağrıldığına bağlı olarak yöntemler farklı davranmaya başladığında karışıklığa neden olabilir.

8
Scott Dorman

Öncelikle işleminizi gerçekleştirdikten sonra (zincirlenebilir, ancak tembel olarak değerlendirilmiş) Select kullanabilirsiniz, ardından kimliğinizi döndürürsünüz (veya tercih ederseniz başka bir şey).

IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"};
people.Select(p => { Console.WriteLine(p); return p; });

Halen değerlendirildiğinden emin olmanız gerekir, ya Count() (afaik numaralandırmak için en ucuz işlem) ya da yine de ihtiyacınız olan başka bir işlem.

Yine de standart kütüphaneye getirildiğini görmek isterdim:

static IEnumerable<T> WithLazySideEffect(this IEnumerable<T> src, Action<T> action) {
  return src.Select(i => { action(i); return i; } );
}

Yukarıdaki kod daha sonra foreach ile etkin olarak eşdeğer olan, ancak tembel ve zincirlenebilir olan people.WithLazySideEffect(p => Console.WriteLine(p)) olur.

7
Martijn

@Coincoin

Foreach uzatma yönteminin gerçek gücü, kodunuza gereksiz yöntemler eklemeden Action<> öğesinin yeniden kullanılabilirliğini içerir. 10 listeniz olduğunu ve bunlara aynı mantığı uygulamak istediğinizi ve ilgili bir fonksiyonun sınıfınıza uymadığını ve tekrar kullanılmadığını söyleyin. Döngüler için on tane olmak ya da ait olmayan bir yardımcı olan genel bir işlev yerine, tüm mantığınızı tek bir yerde tutabilirsiniz (Action<>. Böylece düzinelerce satır değiştirilir)

Action<blah,blah> f = { foo };

List1.ForEach(p => f(p))
List2.ForEach(p => f(p))

vb...

Mantık bir yerde ve sınıfınızı kirletmediniz.

4
diehard2

MoreLINQ NuGet’in aradığınız ForEach eklenti yöntemini sağladığını unutmayın (aynı zamanda yürüten bir Pipe yöntemi Delege ve sonucu verir). Görmek:

4
Dave Clausen

Bununla ilgili bir blog yazısı yazdım: http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx

Bu yöntemi .NET 4.0'da görmek isterseniz, oy kullanabilirsiniz: http://connect.Microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=27909

2
Kirill Osenkov

Ben genişletmek istiyorum Aku'nun cevabı .

Yanlızca, sadece tüm sayıları tekrar etmeden yan etkisi için bir yöntem çağırmak istiyorsanız, bunu kullanabilirsiniz:

private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
    foreach (var x in xs) {
        f(x); yield return x;
    }
}
2
fredefox

3.5'te IEnumerable'a eklenen tüm uzatma yöntemleri LINQ desteği için var (System.Linq.Enumerable sınıfında tanımlandıklarına dikkat edin). Bu yazıda, foreach'in neden LINQ'a ait olmadığını açıklıyorum: Parallel.For? 'A benzer olan mevcut LINQ uzantısı yöntemi?

2
Neil

Bana mı öyle geliyor yoksa Liste <T> mi? Her ne kadar çok Linq tarafından kullanılmıyorsa da. Aslında oradaydı

foreach(X x in Y) 

burada Y, IEnumerable (Ön 2.0) olmalı ve bir GetEnumerator () uygulamıştı. Oluşturulan MSIL’e bakarsanız, bunun tamamen aynı olduğunu görebilirsiniz.

IEnumerator<int> enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
    int i = enumerator.Current;

    Console.WriteLine(i);
}

(Bkz --- http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx MSIL için)

Sonra DotNet2.0 Generics geldi ve Liste. Foreach bana her zaman Vistor modelinin bir uygulaması olduğumu hissetti, (Gamma, Helm, Johnson, Vlissides Tasarım Dizaynlarına bakın).

Şimdi tabii ki 3.5'te aynı etki için bir Lambda kullanabiliriz, örneğin bir deneyin http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and- linq-oh-my /

2
user18784

LINQ uzatma yöntemlerinin çoğu sonuçları verir. ForEach, hiçbir şey döndürmediğinden bu forma uymuyor.

2
leppie

Kısmen bunun nedeni, dil tasarımcılarının felsefi bir bakış açısıyla onunla aynı fikirde olmamasıdır.

  • Bir özelliğe sahip olmamak (ve test etmek ...) bir özelliğe sahip olmaktan daha az iş demektir.
  • Gerçekten daha kısa değil (olduğu yerde bazı işlevsellik vakaları var, ancak bu birincil kullanım olmaz).
  • Amacı, linq ile ilgili olmayan yan etkilere sahip olmaktır.
  • Neden zaten sahip olduğumuz bir özellikle aynı şeyi yapmanın başka bir yolu var? (foreach anahtar kelimesi)

https://blogs.msdn.Microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/

2
McKay

Eğer F # 'ya sahipseniz (ki bu bir sonraki .NET sürümünde olacaktır),

Seq.iter doSomething myIEnumerable

2
TraumaPony

Bir şeyi ne zaman iade etmek istediğinizde select'i kullanabilirsiniz. Bunu yapmazsanız, önce ToList'i kullanabilirsiniz, çünkü muhtemelen koleksiyondaki hiçbir şeyi değiştirmek istemezsiniz.

2
Paco

Foreach kullanmak için listeniz birincil belleğe yüklenmelidir. Ancak IEnumerable'ın tembel yükleme yapısı nedeniyle, IEnumerable'da ForEach sağlamazlar.

Ancak istekli yükleme (ToList () kullanarak), listenizi belleğe yükleyebilir ve ForEach’ten yararlanabilirsiniz.

1
Arjun Bhalodiya

Henüz kimse ForEach <T> 'in foreach anahtar sözcüğünün çalışma zamanı kontrol edildiğinde derleme zamanı tipi kontrolü ile sonuçlandığına işaret etmedi.

Her iki yöntemin de kodda kullanıldığı bazı refactoring işlemleri yaptıktan sonra, foreach problemlerini bulmak için test hataları/çalışma zamanı arızalarını avlamak zorunda kaldığım için.

0
Squirrel