Por que não há um método de extensão ForEach no IEnumerable?

Inspirado por outra pergunta perguntando sobre a function Zip ausente:

Por que não há um método de extensão ForEach na class Enumerable ? Ou em qualquer lugar? A única class que obtém um método ForEach é List . Existe uma razão pela qual está faltando (desempenho)?

Já existe uma declaração foreach incluída na linguagem que faz o trabalho a maior parte do tempo.

Eu odiaria ver o seguinte:

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

Ao invés de:

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

O último é mais claro e mais fácil de ler na maioria das situações , embora talvez seja um pouco mais longo para ser typescript.

No entanto, devo admitir que mudei minha posição sobre esse assunto; um método de extensão ForEach () seria de fato útil em algumas situações.

Aqui estão as principais diferenças entre a declaração e o método:

  • Verificação de tipo: foreach é feito em tempo de execução, ForEach () está em tempo de compilation (Big Plus!)
  • A syntax para chamar um delegado é realmente muito mais simples: objects.ForEach (DoSomething);
  • ForEach () pode ser encadeado: embora a maldade / utilidade de tal recurso esteja aberta à discussão.

Esses são todos pontos importantes feitos por muitas pessoas aqui e eu posso ver porque as pessoas estão perdendo a function. Eu não me importaria que a Microsoft adicionasse um método ForEach padrão na próxima iteração do framework.

ForEach método foi adicionado antes do LINQ. Se você adicionar a extensão ForEach, ela nunca será chamada para instâncias de List por causa das restrições de methods de extensão. Eu acho que a razão pela qual não foi adicionado é não interferir com o já existente.

No entanto, se você realmente sentir falta dessa pequena function legal, você pode lançar sua própria versão

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

Você poderia escrever este método de extensão:

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

Prós

Permite encadeamento:

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

Contras

Não fará nada até que você faça alguma coisa para forçar a iteração. Por esse motivo, não deve ser chamado. .ForEach() . Você poderia escrever .ToList() no final, ou você poderia escrever este método de extensão também:

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

Isso pode ser um desvio muito significativo das bibliotecas C # de envio; os leitores que não estão familiarizados com seus methods de extensão não saberão o que fazer com seu código.

A discussão aqui dá a resposta:

Na verdade, a discussão específica que presenciei de fato dependia da pureza funcional. Em uma expressão, freqüentemente há suposições feitas sobre não ter efeitos colaterais. Tendo ForEach é especificamente convidando efeitos colaterais, em vez de apenas colocar-se com eles. – Keith Farmer (sócio)

Basicamente, a decisão foi tomada para manter os methods de extensão funcionalmente “puros”. Um ForEach encorajaria efeitos colaterais ao usar os methods de extensão Enumerable, o que não era a intenção.

Embora eu concorde que é melhor usar o construtor foreach embutido na maioria dos casos, acho que o uso dessa variação na extensão ForEach <> é um pouco mais legal do que ter que gerenciar o índice de forma regular em relação a mim mesmo:

 public static int ForEach(this IEnumerable list, Action action) { if (action == null) throw new ArgumentNullException("action"); var index = 0; foreach (var elem in list) action(index++, elem); return index; } 

Exemplo

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

Lhe daria:

 Person #0 is Moe Person #1 is Curly Person #2 is Larry 

Uma solução é escrever .ToList().ForEach(x => ...) .

pros

Fácil de entender – o leitor só precisa saber o que vem com o C #, e não qualquer método adicional de extensão.

O ruído sintático é muito leve (adiciona apenas um pequeno código extrangeiro).

Geralmente não custa memory extra, já que um .ForEach() nativo teria que realizar a coleção inteira, de qualquer maneira.

contras

Ordem de operações não é ideal. Eu prefiro perceber um elemento, depois agir e repetir. Esse código realiza todos os elementos primeiro, depois age sobre eles em seqüência.

Se perceber que a lista gera uma exceção, você nunca poderá agir em um único elemento.

Se a enumeração é infinita (como os números naturais), você está sem sorte.

Eu sempre me perguntei por que eu sempre carrego isso comigo:

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

Bom pequeno método de extensão.

Então, tem havido muitos comentários sobre o fato de que um método de extensão ForEach não é apropriado porque não retorna um valor como os methods de extensão LINQ. Embora esta seja uma declaração factual, não é inteiramente verdade.

Os methods de extensão LINQ retornam um valor para que possam ser encadeados:

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

No entanto, só porque LINQ é implementado usando methods de extensão não significa que os methods de extensão devem ser usados ​​da mesma maneira e retornar um valor. Escrever um método de extensão para expor uma funcionalidade comum que não retorna um valor é um uso perfeitamente válido.

O argumento específico sobre ForEach é que, com base nas restrições dos methods de extensão (ou seja, que um método de extensão nunca replaceá um método herdado com a mesma assinatura ), pode haver uma situação em que o método de extensão customizado está disponível em todas as classs IEnumerable >, exceto a lista >. Isso pode causar confusão quando os methods começam a se comportar de maneira diferente dependendo se o método de extensão ou o método herdado está sendo chamado.

Você poderia usar o Select ( Select cadeia, mas preguiçosamente), primeiro fazendo sua operação e, em seguida, retornando a identidade (ou outra coisa, se preferir)

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

Você precisará certificar-se de que ainda é avaliado, seja com Count() (a operação mais barata para enumerar afaik) ou outra operação que você precisa mesmo assim.

Eu adoraria vê-lo trazido para a biblioteca padrão embora:

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

O código acima, em seguida, torna-se people.WithLazySideEffect(p => Console.WriteLine(p)) que é efetivamente equivalente a foreach, mas preguiçoso e de cadeia.

@ Coincoin

O poder real do método de extensão foreach envolve a capacidade de reutilização da Action<> sem adicionar methods desnecessários ao seu código. Digamos que você tenha 10 listas e queira executar a mesma lógica nelas, e uma function correspondente não se encheckbox na sua class e não é reutilizada. Em vez de ter dez loops for, ou uma function genérica que é obviamente um auxiliar que não pertence, você pode manter toda a sua lógica em um único lugar (a Action<> . Então, dezenas de linhas são substituídas por

 Action f = { foo }; List1.ForEach(p => f(p)) List2.ForEach(p => f(p)) 

etc …

A lógica está em um lugar e você não poluiu sua class.

No 3.5, todos os methods de extensão adicionados ao IEnumerable estão lá para suporte LINQ (observe que eles são definidos na class System.Linq.Enumerable). Neste post, eu explico por que foreach não pertence ao LINQ: Existing LINQ extension method similar ao Parallel.For?

Observe que o MoreLINQ NuGet fornece o método de extensão ForEach que você está procurando (assim como um método Pipe que executa o delegado e produz seu resultado). Vejo:

A maioria dos methods de extensão LINQ retorna resultados. ForEach não se encheckbox nesse padrão, pois não retorna nada.

Você pode usar selecionar quando quiser retornar algo. Se não, você pode usar ToList primeiro, porque você provavelmente não quer modificar nada na coleção.

Sou eu ou a List .Foreach praticamente se tornou obsoleta pela Linq. Originalmente havia

 foreach(X x in Y) 

onde Y simplesmente tinha que ser IEnumerable (Pre 2.0) e implementar um GetEnumerator (). Se você olhar para o MSIL gerado, você pode ver que é exatamente o mesmo que

 IEnumerator enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { int i = enumerator.Current; Console.WriteLine(i); } 

(Veja http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx para o MSIL)

Então, em DotNet2.0 Generics veio junto e a lista. Foreach sempre me pareceu uma implementação do padrão Vistor (veja Padrões de Design por Gamma, Helm, Johnson, Vlissides).

Agora, claro, em 3.5, podemos usar um Lambda para o mesmo efeito, por exemplo, tente http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh- meu/

Para usar Foreach, sua lista deve ser carregada na memory principal. Mas devido à natureza de carregamento lento de IEnumerable, eles não fornecem ForEach em IEnumerable.

No entanto, por carregamento antecipado (usando ToList ()), você pode carregar sua lista na memory e aproveitar o ForEach.

Se você tem F # (que estará na próxima versão do .NET), você pode usar

Seq.iter doSomething myIEnumerable

Ninguém ainda apontou que ForEach resulta na verificação do tipo de tempo de compilation, em que a palavra-chave foreach é verificada em tempo de execução.

Tendo feito alguma refatoração em que ambos os methods foram usados ​​no código, eu prefiro .PorEach, como eu tive que caçar falhas de teste / falhas de tempo de execução para encontrar os problemas foreach.

Eu gostaria de expandir a resposta de Aku .

Se você quiser chamar um método com o único propósito de seu efeito colateral sem iterar todo o enumerável primeiro, você pode usar isto:

 private static IEnumerable ForEach(IEnumerable xs, Action f) { foreach (var x in xs) { f(x); yield return x; } }