Equivalente LINQ de foreach para IEnumerable <T>


717

Gostaria de fazer o equivalente ao seguinte no LINQ, mas não consigo descobrir como:

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

Qual é a sintaxe real?


99
Considere "foreach" vs. "ForEach", de Eric Lippert. Ele fornece uma boa justificativa para não usar / fornecer a ForEach().

4
@ pst: Eu costumava cegar esses métodos de extensão em meus projetos. Obrigado por esse artigo.
Robin Maben

2
Há o MoreLINQ que possui uma ForEachextensão .
Uwe Keim

2
Embora não muito sexy, isso se encaixa em uma linha sem criar uma cópia via ToList -foreach (var i in items) i.Dostuff();
James Westgate

1
Alguns desses links estão mortos! Qual foi o artigo que recebeu tantos votos!
Warren

Respostas:


863

Não há extensão ForEach para IEnumerable; apenas para List<T>. Então você poderia fazer

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

Como alternativa, escreva seu próprio método de extensão ForEach:

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

213
Tenha cuidado com ToList (), porque ToList () cria uma cópia da sequência, o que pode causar problemas de desempenho e memória.
Decasteljau

15
Existem poucas razões pelas quais ".Foreach ()" será melhor. 1. Multi-threading, possível pelo PLINQ. 2. Exceções, convém coletar todas as exceções no loop e lançá-las de uma só vez; e eles são códigos de ruído.
Dennis C

12
O PLINQ usa ForAll e não ForEach, e é por isso que você não usaria o ForEach.
user7116

9
Você deve retornar um IENumerable<T>no método de extensão. Como: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
Observe que, no primeiro caso, o desenvolvedor 1) poupa quase nenhuma digitação e 2) aloca desnecessariamente memória para armazenar toda a sequência como uma lista antes de jogá-la fora. Pense antes de você LINQ
Mark Sowul

364

Fredrik forneceu a correção, mas pode valer a pena considerar por que isso não está na estrutura, para começar. Acredito que a idéia é que os operadores de consulta LINQ sejam livres de efeitos colaterais, adaptando-se a uma maneira razoavelmente funcional de olhar o mundo. Claramente, o ForEach é exatamente o oposto - uma construção puramente baseada em efeitos colaterais.

Isso não quer dizer que isso é uma coisa ruim a se fazer - basta pensar nas razões filosóficas por trás da decisão.


44
E ainda, o F # tem o Seq.iter
Stephen Swensen

6
No entanto, há uma coisa muito útil que as funções LINQ normalmente fornecem que foreach () não - a função anônima usada pelas funções Linq também pode receber um índice como parâmetro, enquanto que com foreach você deve reconstruir o clássico para (int .. ) construir. E as linguagens funcionais precisam permitir a iteração que tem efeitos colaterais - caso contrário, você nunca poderia trabalhar com eles :) Gostaria de salientar que o método funcional típico seria retornar o enumerável original inalterado.
Walt W

3
@Walt: Ainda não tenho certeza se concordo, em parte porque uma abordagem verdadeiramente funcional típica não usaria uma para uma dessas coisas ... ela usaria uma abordagem funcional mais como LINQ, criando uma nova sequência.
Jon Skeet

12
@ Stephen Swensen: sim, o F # possui Seq.iter, mas o F # também possui estruturas de dados imutáveis. ao usar o Seq.iter, o Seq original não é modificado; uma nova Seq com os elementos afetados pela lateral é retornada.
Anders

7
@ Anders - Seq.iternão retorna nada, muito menos um novo seq com elementos afetados pela lateral. É realmente apenas sobre efeitos colaterais. Você pode estar pensando Seq.map, porque Seq.iteré precisamente equivalente a um ForEachmétodo de extensão ativado IEnumerabe<_>.
Joel Mueller #

39

Atualização 7/17/2012: Aparentemente, a partir do C # 5.0, o comportamento foreachdescrito abaixo foi alterado e " o uso de uma foreachvariável de iteração em uma expressão lambda aninhada não produz mais resultados inesperados " . Esta resposta não se aplica ao C # ≥ 5.0 .

@ John Skeet e todos que preferem a palavra-chave foreach.

O problema com "foreach" em C # anterior à 5.0 é que ele é inconsistente com o modo como a "compreensão" equivalente funciona em outros idiomas e com o que eu esperaria que funcionasse (opinião pessoal declarada aqui apenas porque outros mencionaram seus opinião sobre legibilidade). Consulte todas as perguntas sobre " Acesso ao fechamento modificado " e " Variável sobre o loop considerada prejudicial ". Isso é apenas "prejudicial" devido à maneira como "foreach" é implementado em C #.

Pegue os exemplos a seguir, usando o método de extensão funcionalmente equivalente ao da resposta de @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);
        }
    }
}

Desculpas pelo exemplo excessivamente artificial. Só estou usando o Observable porque não é totalmente buscado fazer algo assim. Obviamente, existem maneiras melhores de criar isso observável, estou apenas tentando demonstrar um ponto. Normalmente, o código inscrito no observável é executado de forma assíncrona e potencialmente em outro encadeamento. Se usar "foreach", isso pode produzir resultados muito estranhos e potencialmente não determinísticos.

O seguinte teste usando o método de extensão "ForEach" passa:

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

O seguinte falha com o erro:

Esperado: equivalente a <0, 1, 2, 3, 4, 5, 6, 7, 8, 9> Mas foi: <9, 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()));
}

O que diz o aviso de novo compartilhamento?
reggaeguitar

1
@ reggaeguitar Eu não uso o C # há 7 ou 8 anos, desde antes do lançamento do C # 5.0, o que mudou o comportamento descrito aqui. Na época, ele emitiu esse aviso stackoverflow.com/questions/1688465/… . Duvido que ainda avise sobre isso.
drstevens 12/06/19

34

Você pode usar a FirstOrDefault()extensão, que está disponível para IEnumerable<T>. Ao retornar falsedo predicado, ele será executado para cada elemento, mas não se importará em não encontrar uma correspondência. Isso evitará a ToList()sobrecarga.

IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });

11
Também concordo. Pode ser um pequeno truque inteligente, mas à primeira vista, esse código não está claro o que está fazendo, certamente em comparação com um loop foreach padrão.
11302 Connell

26
Eu tive que downvote porque enquanto tecnicamente correta, o seu próprio futuro e todos os seus sucessores vão te odiar para sempre, porque em vez de foreach(Item i in GetItems()) { i.DoStuff();}você tomou mais caracteres e tornou extremamente confuso
Mark Sowul

14
Ok, mas prolly um hack mais legível:items.All(i => { i.DoStuff(); return true; }
Nawfal

5
Eu sei que este é um post bastante antigo, mas também tive que rebaixá-lo. Concordo que é um truque inteligente, mas não é sustentável. À primeira vista, parece que ele está fazendo algo apenas com o primeiro item da lista. Isso poderia facilmente causar mais erros no futuro, se os codificadores não entenderem como ele funciona e o que deveria estar fazendo.
Lee

4
Esta é a programação de efeitos colaterais e o IMHO não é recomendado.
Anish

21

Peguei o método de Fredrik e modifiquei o tipo de retorno.

Dessa forma, o método suporta execução adiada como outros métodos LINQ.

Edição: Se isso não ficou claro, qualquer uso desse método deve terminar com ToList () ou qualquer outra maneira de forçar o método a trabalhar no enumerável completo. Caso contrário, a ação não seria executada!

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

E aqui está o teste para ajudar a vê-lo:

[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"));
}

Se você remover o ToList () no final, verá o teste falhando, pois o StringBuilder contém uma sequência vazia. Isso ocorre porque nenhum método forçou o ForEach a enumerar.


Sua implementação alternativa de ForEaché interessante, mas não corresponde ao comportamento de List.ForEach, cuja assinatura é public void ForEach(Action<T> action). Também não corresponde ao comportamento da Observable.ForEachextensão IObservable<T>, cuja assinatura é public static void ForEach<TSource>(this IObservable<TSource> source, Action<TSource> onNext). O FWIW, o ForEachequivalente nas coleções Scala, mesmo as preguiçosas, também possui um tipo de retorno equivalente a nulo em C #.
drstevens

Esta é a melhor resposta: execução adiada + API fluente.
Alexander Danilov

3
Isso funciona exatamente da mesma maneira que ligar Selectcom ToList. O objetivo ForEaché não precisar ligar ToList. Deve ser executado imediatamente.
t3chb0t

3
Qual seria o benefício de ter esse "ForEach", cuja execução é adiada, versus um que é executado imediatamente? Adiar (o que é inconveniente na forma como o ForEach será usado) não melhora o fato de que o ForEach só executa um trabalho útil se a Ação tiver efeitos colaterais [a reclamação usual feita contra um operador Linq]. Então, como faz essa mudança ajuda? Além disso, o "ToList ()" adicionado para forçá-lo a executar não serve para nada. É um acidente esperando para acontecer. O ponto de "ForEach" é seus efeitos colaterais. Se você deseja uma enumeração retornada, SELECT faz mais sentido.
Home

20

Mantenha seus efeitos colaterais fora do meu IEnumerable

Gostaria de fazer o equivalente ao seguinte no LINQ, mas não consigo descobrir como:

Como outros já apontaram aqui e no exterior, IEnumerableespera-se que o LINQ e os métodos sejam livres de efeitos colaterais.

Deseja realmente "fazer alguma coisa" para cada item no IEnumerable? Então foreaché a melhor escolha. As pessoas não ficam surpresas quando os efeitos colaterais acontecem aqui.

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

Aposto que você não quer um efeito colateral

No entanto, na minha experiência, efeitos colaterais geralmente não são necessários. Na maioria das vezes, existe uma consulta LINQ simples aguardando descoberta, acompanhada por uma resposta do StackOverflow.com por Jon Skeet, Eric Lippert ou Marc Gravell explicando como fazer o que você deseja!

Alguns exemplos

Se você está apenas agregando (acumulando) algum valor, considere o Aggregatemétodo de extensão.

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

Talvez você queira criar um novo a IEnumerablepartir dos valores existentes.

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

Ou talvez você queira criar uma tabela de consulta:

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

A lista (trocadilhos não totalmente intencionados) de possibilidades continua.


7
Ah, então Select é Map e Aggregate é Reduce.
Darrel Lee

17

Se você deseja atuar como os rolos de enumeração, deve gerar cada item.

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

Este não é realmente um ForEach quando você devolve o item, é mais como um Tap.
EluciusFTW

10

Existe uma versão experimental da Microsoft das extensões interativas para o LINQ (também no NuGet , consulte o perfil do RxTeams para obter mais links). O vídeo do Channel 9 explica bem.

Seus documentos são fornecidos apenas no formato XML. Eu executei esta documentação no Sandcastle para permitir que ela esteja em um formato mais legível. Descompacte o arquivo morto do docs e procure index.html .

Entre muitas outras vantagens, ele fornece a implementação ForEach esperada. Permite escrever código como este:

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

numbers.ForEach(x => Console.WriteLine(x*x));

2
O link de trabalho para extensões interativas: microsoft.com/download/en/details.aspx?id=27203
Maciek Świszczowski

8

De acordo com o PLINQ (disponível desde .Net 4.0), você pode fazer uma

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

para executar um loop foreach paralelo em um IEnumerable.


desde que sua ação seja segura, tudo está bem. mas o que acontecerá se você tiver uma ação não segura para executar? ele pode causar um caos ...
shirbr510

2
Verdade. Não é seguro para threads, threads diferentes serão usados ​​em algum pool de threads ... E eu preciso revisar minha resposta. Não existe ForEach () quando o tento agora. Deve ter sido meu código contendo uma Extensão com ForEach quando pensei nisso.
Wolf5

6

O objetivo do ForEach é causar efeitos colaterais. IEnumerable é para enumeração lenta de um conjunto.

Essa diferença conceitual é bastante visível quando você a considera.

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

Isso não será executado até que você faça uma "contagem" ou um "ToList ()" ou algo assim. Claramente não é o que é expresso.

Você deve usar as extensões IEnumerable para configurar cadeias de iteração, definindo o conteúdo por suas respectivas fontes e condições. As árvores de expressão são poderosas e eficientes, mas você deve aprender a apreciar sua natureza. E não apenas para programar em torno deles para salvar alguns caracteres substituindo a avaliação preguiçosa.


5

Muitas pessoas mencionaram, mas eu tive que escrever. Isso não é mais claro / mais legível?

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

Curto e simples (st).


Você pode deixar cair toda a primeira linha e use GetItems diretamente no foreach
tymtam

3
Claro. Está escrito assim para maior clareza. Para que fique claro qual GetItems()método retorna.
Nenad

4
Agora, quando leio meu comentário, vejo que parece que estou lhe dizendo algo que você não sabe. Eu não quis dizer isso. O que eu quis dizer é que foreach (var item in GetItems()) item.DoStuff();é apenas uma beleza.
precisa saber é o seguinte

4

Agora temos a opção de ...

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

Obviamente, isso abre uma nova lata de threadworms.

ps (Desculpe pelas fontes, foi o que o sistema decidiu)


4

Como inúmeras respostas já apontam, você pode adicionar facilmente esse método de extensão. No entanto, se você não quiser fazer isso, embora eu não esteja ciente de algo assim na BCL, ainda há uma opção no Systemespaço para nome, se você já tiver uma referência à Extensão Reativa (e se não quiser). , Você devia ter):

using System.Reactive.Linq;

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

Embora os nomes dos métodos sejam um pouco diferentes, o resultado final é exatamente o que você está procurando.


Parece que esse pacote está obsoleto agora
reggaeguitar 10/06/19

3

O ForEach também pode ser acorrentado , basta retornar à linha de pilotos após a ação. permaneça fluente


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

Uma versão ansiosa da implementação.

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: um preguiçoso versão está usando retorno de rendimento, como este .

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

A versão Lazy PRECISA ser materializada, ToList () por exemplo, caso contrário, nada acontece. veja abaixo ótimos comentários de ToolmakerSteve.

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

Eu mantenho ForEach () e ForEachLazy () na minha biblioteca.


2
Você já testou isso? Existem vários contextos em que esse método se comporta contra intuitivamente. Melhor seriaforeach(T item in enu) {action(item); yield return item;}
Taemyr

2
Isto irá resultar em vários enumerações
regisbsb

Interessante. Ponderando quando eu gostaria de fazer o acima, para uma sequência de 3 ações, em vez de foreach (T item in enu) { action1(item); action2(item); action3(item); }? Um único loop explícito. Eu acho que se era importante fazer todas as ações1 antes de começar a fazer ações2.
Home

@ Rm558 - a sua abordagem usando "yield return". Pro: apenas enumera a sequência original uma vez, portanto, funciona corretamente com uma ampla variedade de enumerações. Con: se você esquecer ".ToArray ()" no final, nada acontece. Embora a falha seja óbvia, não será esquecida por muito tempo, portanto, não é um custo importante. Não me "sente limpo", mas é a troca certa, se alguém estiver seguindo esse caminho (um operador do ForEach).
Home

1
Este código não é seguro do ponto de vista transacional. E se ele falhar ProcessShipping()? Você envia um e-mail ao comprador, cobra o cartão de crédito, mas nunca envia as coisas para ele? Certamente pedindo problemas.
Zmechanic

2

Essa abstração de "abordagem funcional" vaza muito tempo. Nada no nível do idioma evita efeitos colaterais. Contanto que você possa chamar seu lambda / delegado para cada elemento no contêiner - você obterá o comportamento "ForEach".

Aqui, por exemplo, uma maneira de mesclar srcDictionary em destDictionary (se a chave já existir - substitui)

isso é um hack e não deve ser usado em nenhum código de produção.

var b = srcDictionary.Select(
                             x=>
                                {
                                  destDictionary[x.Key] = x.Value;
                                  return true;
                                }
                             ).Count();

6
Se você precisar adicionar esse aviso, provavelmente não deve publicá-lo. Apenas minha opinião.
Andrew Grothe

2
Como diabos isso é melhor do que foreach(var tuple in srcDictionary) { destDictionary[tuple.Key] = tuple.Value; }? Se não estiver no código de produção, onde e por que você deveria usá-lo?
Mark Sowul

3
Isso apenas para mostrar que é possível em princípio. Também acontece o que o OP solicitou, e indiquei claramente que não deve ser usado na produção, ainda curioso e com valor educacional.
Zar Shardan

2

Inspirado por Jon Skeet, estendi sua solução com o seguinte:

Método de extensão:

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

Cliente:

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

Eu discordo respeitosamente da noção de que os métodos de extensão de link devem ser livres de efeitos colaterais (não apenas porque não são, qualquer delegado pode executar efeitos colaterais).

Considere o seguinte:

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

O que o exemplo mostra é realmente apenas um tipo de ligação tardia que permite invocar uma das muitas ações possíveis com efeitos colaterais em uma sequência de elementos, sem precisar escrever uma grande construção de chave para decodificar o valor que define a ação e traduzir no seu método correspondente.


9
Há uma diferença entre se um método de extensão PODE ter efeitos colaterais e se DEVE ter. Você está apenas apontando que isso pode ter, embora possa haver boas razões para propor que eles não tenham efeitos colaterais. Na programação funcional, todas as funções são livres de efeitos colaterais e, ao usar construções de programação funcional, você pode assumir que elas são.
Dave Van den Eynde

1

Para permanecer fluente, pode-se usar esse truque:

GetItems()
    .Select(i => new Action(i.DoStuf)))
    .Aggregate((a, b) => a + b)
    .Invoke();

0

Para o VB.NET, você deve usar:

listVariable.ForEach(Sub(i) i.Property = "Value")

NOTA: Isso compila apenas se você tiver um Listou IList. Para geral IEnumerable, escreva o equivalente em VB do método de extensão ForEach da resposta aceita .
Home

-1

Mais um ForEachexemplo

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

4
Que desperdício de memória. Isso chama ToList para adicionar a uma lista. Por que isso não retorna apenas endereços.Selecione (a => MapToDomain (a))?
Mark Sowul 29/07
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.