LINQ equivalente de un foreach para IEnumerable


Me gustaría hacer el equivalente de lo siguiente en LINQ, pero no puedo averiguar cómo:

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

¿Cuál es la sintaxis real?

Author: abatishchev, 2008-10-14

20 answers

No hay ninguna extensión ForEach para IEnumerable; solo para List<T>. Así que usted podría hacer

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

Alternativamente, escriba su propio método de extensión ForEach:

public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach(T item in enumeration)
    {
        action(item);
    }
}
 744
Author: Fredrik Kalseth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-29 08:37:48

Fredrik ha proporcionado la solución, pero puede valer la pena considerar por qué esto no está en el marco para empezar. Creo que la idea es que los operadores de consulta LINQ deben estar libres de efectos secundarios, encajando con una forma razonablemente funcional de ver el mundo. Claramente ForEach es exactamente lo contrario-un puramente construcción basada en efectos secundarios.

Eso no quiere decir que esto sea algo malo - solo pensar en las razones filosóficas detrás de la decisión.

 330
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-14 10:10:51

Actualización 17/7/2012: Aparentemente a partir de C# 5.0, el comportamiento de foreach descrito a continuación ha cambiado y " el uso de una variable de iteración foreach en una expresión lambda anidada ya no produce resultados inesperados." Esta respuesta no se aplica a C# ≥ 5.0.

@John Skeet y todos los que prefieren la palabra clave foreach.

El problema con "foreach" en C# antes de 5.0, es que es inconsistente con cómo el equivalente " para la comprensión" funciona en otros idiomas, y con cómo esperaría que funcionara (opinión personal expresada aquí solo porque otros han mencionado su opinión con respecto a la legibilidad). Ver todas las preguntas sobre " Acceso al cierre modificado " así como "Cerrando sobre la variable de bucle considerada dañina". Esto solo es " dañino "debido a la forma en que" foreach " se implementa en C#.

Tome los siguientes ejemplos usando el método de extensión funcionalmente equivalente al de Respuesta 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);
        }
    }
}

Disculpas por el ejemplo demasiado artificial. Solo estoy usando Observable porque no es del todo descabellado hacer algo como esto. Obviamente hay mejores maneras de crear este observable, solo estoy tratando de demostrar un punto. Normalmente, el código suscrito al observable se ejecuta de forma asíncrona y potencialmente en otro subproceso. Si se utiliza "foreach", esto podría producir muy extraño y potencialmente no determinista resultado.

La siguiente prueba usando el método de extensión" ForEach"pasa:

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

Lo siguiente falla con el error:

Esperado: equivalente a Pero fue:

[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()));
}
 33
Author: drstevens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:29

Puede usar la extensión FirstOrDefault(), que está disponible para IEnumerable<T>. Al devolver false desde el predicado, se ejecutará para cada elemento, pero no le importará que en realidad no encuentre una coincidencia. Esto evitará la sobrecarga ToList().

IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });
 31
Author: Rhames,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-12 10:39:40

Tomé Fredrik del método y modificado el tipo de retorno.

De esta manera, el método soporta ejecución diferida como otros métodos LINQ.

EDIT: Si esto no estaba claro, cualquier uso de este método debe terminar con ToList() o cualquier otra forma de forzar al método a trabajar en el enumerable completo. De lo contrario, la acción no se realizaría!

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

Y aquí está la prueba para ayudar a verlo:

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

Si elimina el ToList () en al final, verá que la prueba falla ya que el StringBuilder contiene una cadena vacía. Esto se debe a que ningún método obligó al ForEach a enumerar.

 19
Author: Dor Rotman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-03 16:39:27

Mantenga sus efectos secundarios fuera de miumumerable

Me gustaría hacer el equivalente de lo siguiente en LINQ, pero no puedo averiguar cómo:

Como otros han señalado aquí y en el extranjero se espera que los métodos LINQ y IEnumerable estén libres de efectos secundarios.

¿Realmente quieres "hacer algo" a cada elemento en elumumerable? Entonces foreach es la mejor opción. La gente no se sorprende cuando los efectos secundarios ocurren aquí.

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

Apuesto a que no quieres un efecto secundario

Sin embargo, en mi experiencia, los efectos secundarios generalmente no son necesarios. La mayoría de las veces hay una simple consulta LINQ esperando ser descubierta acompañada de un StackOverflow.com respuesta de Jon Skeet, Eric Lippert, o Marc Gravell explicando cómo hacer lo que quieres!

Algunos ejemplos

Si en realidad solo está agregando (acumulando) algún valor, entonces debe considerar la extensión Aggregate método.

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

Tal vez desee crear un nuevo IEnumerable a partir de los valores existentes.

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

O tal vez quieras crear una tabla de búsqueda:

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

La lista (juego de palabras no del todo intencionado) de posibilidades sigue y sigue.

 13
Author: cdiggins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:59

Hay una versión experimental de Microsoft de Extensiones interactivas a LINQ (también en NuGet, ver perfil de RxTeams para más enlaces). El vídeo del Canal 9 lo explica bien.

Sus documentos solo se proporcionan en formato XML. He ejecutado esta documentación en Sandcastle para permitir que esté en un formato más legible. Descomprima el archivo docs y busque index.html .

Entre muchas otras cosas, proporciona la esperada Para cada implementación. Te permite escribir código como este:

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

numbers.ForEach(x => Console.WriteLine(x*x));
 8
Author: John Wigger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-06 04:27:51

Si desea actuar como los rollos de enumeración, debe ceder cada elemento.

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;
        }
    }
}
 7
Author: regisbsb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-31 16:04:53

De acuerdo con PLINQ (disponible desde. Net 4.0), puede hacer un

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

Para hacer un paralelo bucle foreach en un IEnumerable.

 7
Author: Wolf5,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-17 14:10:26

El propósito de ForEach es causar efectos secundarios. IEnumerable es para perezosos enumeración de un conjunto.

Esta diferencia conceptual es bastante visible cuando la consideras.

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

Esto no se ejecutará hasta que hagas un "count" o un "ToList()" o algo en él. Claramente no es lo que se expresa.

Debe usar las extensioneserableumerables para configurar cadenas de iteración, definiendo el contenido por sus respectivas fuentes y condiciones. Los Árboles de Expresión son potente y eficiente, pero usted debe aprender a apreciar su naturaleza. Y no solo para la programación a su alrededor para salvar a unos pocos personajes que anulan la evaluación perezosa.

 5
Author: Tormod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-09-01 08:46:38

Como numerosas respuestas ya señalan, puede agregar fácilmente dicho método de extensión usted mismo. Sin embargo, si no quieres hacer eso, aunque no soy consciente de nada como esto en la BCL, todavía hay una opción en el espacio de nombres System, si ya tienes una referencia a Extensión Reactiva (y si no lo haces, deberías tener):

using System.Reactive.Linq;

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

Aunque los nombres de los métodos son un poco diferentes, el resultado final es exactamente lo que está buscando.

 4
Author: Mark Seemann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-05 20:27:29

Mucha gente lo mencionó, pero tuve que escribirlo. No es esto más claro/más legible?

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

Corto y simple(st).

 3
Author: Nenad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-08 22:47:32

Ahora tenemos la opción de...

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

Por supuesto, esto abre una nueva lata de gusanos.

Ps (Lo siento por las fuentes, es lo que el sistema decidió)

 2
Author: Paulustrious,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-05 17:47:48

Inspirado por Jon Skeet, he ampliado su solución con lo siguiente:

Método De Extensión:

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
Author: Scott Nimrod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-11 00:45:30

ForEach también puede serEncadenado , simplemente vuelva a la pileline después de la acción. mantener la fluidez


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

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
}

Edit2 el código anterior está funcionando, pero una versión mejor está usando este.

Edit a continuación hay un ejemplo equivocado, señalado por Taemyr. Muchas gracias.

Employees.ForEach(e=>e.Salary = e.Salary * 2)
         .Where (e=> e.Salary > 10000)
         .Average(e=> e.Salary);

 1
Author: Rm558,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:44

No estoy de acuerdo con la noción de que los métodos de extensión de enlace deben ser libres de efectos secundarios (no solo porque no lo son, cualquier delegado puede realizar efectos secundarios).

Considere lo siguiente:

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

Lo que muestra el ejemplo es realmente solo una especie de enlace tardío que permite invocar una de las muchas acciones posibles que tienen efectos secundarios en una secuencia de elementos, sin tener que escribir una construcción de gran interruptor para decodificar el valor que define la acción y traducirla en su método correspondiente.

 0
Author: 2 revscaddzooks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-10 03:49:28

Esta abstracción de "enfoque funcional" se filtra a lo grande. Nada en el nivel del idioma previene los efectos secundarios. Siempre que pueda hacer que llame a su lambda / delegate para cada elemento en el contenedor, obtendrá el comportamiento "ForEach".

Aquí, por ejemplo, una forma de fusionar srcDictionary en destDictionary (si la clave ya existe - sobrescribe)

Esto es un truco, y no debe ser utilizado en ningún código de producción.

var b = srcDictionary.Select(
                             x=>
                                {
                                  destDictionary[x.Key] = x.Value;
                                  return true;
                                }
                             ).Count();
 0
Author: Zar Shardan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-03 00:47:40

Para VB.NET usted debe utilizar:

listVariable.ForEach(Sub(i) i.Property = "Value")
 0
Author: Israel Margulies,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-14 19:41:58

Otro ForEach Ejemplo

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
Author: neil martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-17 08:20:22

Si estás haciendo esto, por ejemplo, porque necesitas el índice en tu iteración, siempre puedes usar una construcción Where:

linqObject.Where((obj, index) => {
  DoWork(obj, index);
  return true;
}).ToArray(); //MUST CALL ToArray() or ToList() or something to execute the lazy query, or the loop won't actually execute

Esto tiene el beneficio añadido de que la matriz original se devuelve "sin cambios" (los objetos referenciados por la lista son los mismos, aunque pueden no tener los mismos datos), lo que a menudo es deseable en metodologías de programación funcional / de cadena como LINQ.

 -2
Author: Walt W,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-09-01 17:26:18