Pregunta

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

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

¿Cuál es la sintaxis real?

¿Fue útil?

Solución

No hay una extensión de ForEach para IEnumerable ; solo para List < T > . Para que pudieras 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);
    }
}

Otros consejos

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 no deberían tener efectos secundarios, y que se ajusten a una forma razonablemente funcional de ver el mundo. Claramente, ForEach es exactamente lo contrario: una construcción puramente basada en efectos secundarios.

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

Actualización 17/07/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 # & # 8805; 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 cómo esperaría que funcionara (opinión personal expresada aquí solo porque otros han mencionado su opinión sobre la legibilidad). Consulte todas las preguntas relacionadas con " Acceso al cierre modificado " así como " Cerrar por la variable de bucle considerado dañino " ;. Esto es solo " nocivo " debido a la forma " foreach " se implementa en C #.

Tome los siguientes ejemplos utilizando el método de extensión funcionalmente equivalente al de la 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 excesivamente artificial. Solo estoy usando Observable porque no es del todo descabellado hacer algo como esto. Obviamente, hay mejores formas de crear este observable, solo estoy tratando de demostrar un punto. Por lo general, el código suscrito al observable se ejecuta de forma asíncrona y potencialmente en otro hilo. Si se usa foreach, esto podría producir resultados muy extraños y potencialmente no deterministas.

La siguiente prueba con " ForEach " método de extensión 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 < 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 >    Pero fue: < 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()));
}

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 de ToList () .

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

Tomé el método de Fredrik y modifiqué el tipo de retorno.

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

EDITAR: Si esto no estaba claro, cualquier uso de este método debe terminar con ToList () o cualquier otra forma de forzar el método para que funcione por completo enumerable 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 ToList () al final, verá que la prueba falla ya que StringBuilder contiene una cadena vacía. Esto se debe a que ningún método obligó a ForEach a enumerar.

Mantenga sus efectos secundarios fuera de mi IEnumerable

  
    

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

  

Como otros han señalado aquí y en el extranjero, se espera que LINQ y IEnumerable sean Libre de efectos secundarios.

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

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

Apuesto a que no quieres un efecto secundario

Sin embargo, en mi experiencia, generalmente no se requieren efectos secundarios. La mayoría de las veces, hay una consulta LINQ simple que espera ser descubierta acompañada por una respuesta de StackOverflow.com de Jon Skeet, Eric Lippert o Marc Gravell, que explica cómo hacer lo que usted quiere.

Algunos ejemplos

Si en realidad solo está agregando (acumulando) algún valor, entonces debería considerar el método de extensión Aggregate .

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

Quizás desee crear un nuevo IEnumerable a partir de los valores existentes.

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

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

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

La lista (juego de palabras no totalmente previsto) de posibilidades sigue y sigue.

Si desea actuar como rollos de enumeración, debe generar 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;
        }
    }
}

Hay una versión experimental de Microsoft de Extensiones interactivas a LINQ (también en NuGet , consulte Perfil de RxTeams para más enlaces). El video 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 de documentos y busque index.html .

Entre muchos otros beneficios, proporciona la implementación esperada de ForEach. Le permite escribir código como este:

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

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

Según PLINQ (disponible desde .Net 4.0), puede hacer un

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

para hacer un bucle foreach paralelo en un IEnumerable.

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

Esta diferencia conceptual es bastante visible cuando se considera.

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

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

Debe usar las extensiones IEnumerable para configurar cadenas de iteración, definiendo el contenido por sus respectivas fuentes y condiciones. Los árboles de expresión son poderosos y eficientes, pero debes aprender a apreciar su naturaleza. Y no solo para la programación a su alrededor para guardar algunos caracteres que superan la evaluación perezosa.

Muchas personas lo mencionaron, pero tuve que escribirlo. ¿No es esto lo más claro / lo más legible?

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

Corto y simple (st).

Como ya señalan numerosas respuestas, usted mismo puede agregar fácilmente dicho método de extensión. Sin embargo, si no quiere hacer eso, aunque no conozco nada como esto en el BCL, todavía hay una opción en el espacio de nombres System , si ya tiene una referencia a < a href = "https://www.nuget.org/packages/Rx-Main" rel = "nofollow noreferrer"> Extensión reactiva (y si no lo hace, debería 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ás buscando.

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 de hilo.

ps (Perdón por las fuentes, es lo que decidió el sistema)

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

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

esto es un truco y no debe usarse en ningún código de producción.

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

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

Client:

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

ForEach también puede ser Encadenado , simplemente vuelva a colocarlo en la pila después de la acción. sigue siendo fluido


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

Una versión Eager de la implementación.

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
}

Editar: una versión Lazy está utilizando el rendimiento, como esta .

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

La versión Lazy NECESITA materializarse, ToList () por ejemplo, de lo contrario, no pasa nada. vea debajo los excelentes comentarios 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();

Mantengo tanto ForEach () como ForEachLazy () en mi biblioteca.

Estoy totalmente en desacuerdo con la idea de que los métodos de extensión de enlace deben estar libres de efectos secundarios (no solo porque no lo están, 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 en realidad solo un tipo 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 interruptor grande para descodificar el valor que define el Acción y traducirlo a su método correspondiente.

Para VB.NET debe usar:

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

Para mantener la fluidez, uno puede usar ese truco:

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

MoreLinq tiene IEnumerable < T > .ForEach y un montón de otras extensiones útiles. Probablemente no valga la pena tomar la dependencia solo para ForEach , pero hay muchas cosas útiles allí.

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

https://github.com/morelinq/MoreLINQ

Sin embargo, 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;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top