it-swarm-pt.tech

Equivalente LINQ de foreach para IEnumerable <T>

Eu 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?

645
tags2k

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);
    }
}
782
Fredrik Kalseth

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

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

O problema com "foreach" em C # anterior a 5.0, é que ele é inconsistente com o modo como o equivalente "para compreensão" funciona em outros idiomas e com como eu esperaria que funcionasse (opinião pessoal indicado aqui apenas porque outros mencionaram sua opinião sobre a legibilidade). Veja todas as questões referentes a " Acesso ao fechamento modificado " , Bem como " Fechamento da variável de malha considerada prejudicial ". Isso é apenas "prejudicial" por causa da maneira como "foreach" é implementado em C #.

Tome os seguintes exemplos usando o método de extensão funcionalmente equivalente àquele na 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 inventado. Eu só estou usando Observable porque não é totalmente improvável fazer algo assim. Obviamente, existem maneiras melhores de criar este 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 thread. 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>

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

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

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

Eu peguei o método de Fredrik e modifiquei o tipo de retorno.

Desta forma, o método suporta execução deferred como outros métodos LINQ.

EDIT: 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 ver:

[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, você verá o teste falhando já que o StringBuilder contém uma string vazia. Isso ocorre porque nenhum método forçou o ForEach a enumerar.

19
Dor Rotman

Se você quiser atuar como os rolos de enumeração, deve render 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;
        }
    }
}
9
regisbsb

Existe um lançamento experimental da Microsoft de Interactive Extensions to LINQ (também no NuGet , veja perfil do RxTeams para mais links). O Vídeo do Canal 9 explica bem.

Seus documentos são fornecidos apenas no formato XML. Eu executei este documentação em Sandcastle para permitir que ele seja em um formato mais legível. Descompacte o arquivo docs e procure por index.html.

Entre muitas outras guloseimas, fornece a implementação esperada do ForEach. Ele permite que você escreva um código como este:

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

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

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

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

para fazer um loop foreach paralelo em um IEnumerable.

7
Wolf5

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

Essa diferença conceitual é bem 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 "ToList ()" ou algo sobre ele. Isso 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 origens e condições. Á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, sobrescrevendo a avaliação preguiçosa.

5
Tormod

Muitas pessoas mencionaram isso, mas eu tive que escrevê-lo. Isso não é mais claro/legível?

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

Curto e simples (st).

4
Nenad

Como várias respostas já apontam, você pode adicionar esse método de extensão facilmente. No entanto, se você não quiser fazer isso, embora eu não esteja ciente de nada como isso no BCL, ainda há uma opção no namespace System, se você já tiver uma referência a Ramal reativo (e se você não, você deveria 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.

4
Mark Seemann

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

Claro, isso abre uma nova lata de threadworms.

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

2
Paulustrious

Ainda outro ForEach Exemplo

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
neil martin

Inspirado por Jon Skeet, eu 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
Scott Nimrod

Eu discordo de forma respeitosa com a 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 ter 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 em seu método correspondente.

0
caddzooks

MoreLinq tem IEnumerable<T>.ForEach e uma tonelada de outras extensões úteis. Provavelmente não vale a pena apenas para ForEach, mas há muitas coisas úteis lá.

https://www.nuget.org/packages/morelinq/

https://github.com/morelinq/MoreLINQ

0
solublefish

Para ficar fluente, pode-se usar um truque desse tipo:

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

Para VB.NET você deve usar:

listVariable.ForEach(Sub(i) i.Property = "Value")
0
Israel Margulies

Esta abstração "abordagem funcional" vaza grande momento. Nada no nível da linguagem evita efeitos colaterais. Contanto que você consiga chamar seu lambda/delegate 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 - sobrescrever)

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();
0
Zar Shardan