Pregunta El equivalente LINQ de foreach para IEnumerable


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?


606
2017-10-14 09:56


origen


Respuestas:


No hay extensión ForEach para IEnumerable; solo para List<T>. Entonces podrías 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);
    }
}

733
2017-10-14 10:00



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 consultas LINQ no tengan efectos secundarios, que encajen con una forma razonablemente funcional de mirar el mundo. Claramente, ForEach es exactamente lo opuesto: un puramente construcción basada en efectos secundarios.

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


329
2017-10-14 10:10



Actualizar 7/17/2012: Aparentemente a partir de C # 5.0, el comportamiento de foreach se describe a continuación ha sido cambiado y "el uso de un foreach La variable de iteración 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 no es coherente con la forma en que funciona el equivalente "para la comprensión" en otros idiomas, y con la forma en que esperaría que funcionara (opinión personal aquí expresada solo porque otros han mencionado su opinión con respecto a la legibilidad). Ver todas las preguntas concernientes a "Acceso al cierre modificado" tanto como "Cerrando sobre la variable de lazo considerada dañina"Esto es solo" dañino "debido a la forma en que se implementa" foreach "en C #.

Tome los siguientes ejemplos usando 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 demasiado 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 intentando demostrar un punto. Normalmente, el código suscrito al observable se ejecuta de forma asincrónica y potencialmente en otro hilo. Si usa "foreach", esto podría producir resultados muy extraños y potencialmente no deterministas.

Se pasa la siguiente prueba con el método de extensión "ForEach":

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

33
2017-09-01 19:24



Podrías usar el FirstOrDefault() extensión, que está disponible para IEnumerable<T>. Al regresar false del predicado, se ejecutará para cada elemento, pero no le importará que no encuentre una coincidencia. Esto evitará el ToList() gastos generales.

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

31
2017-10-05 14:16



Tomé el método de Fredrik y modifiqué el tipo de devolución.

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 de cualquier otra manera para obligar 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 esta es 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 eliminas el Listar() 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.


19
2018-06-02 08:54



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 LINQ y IEnumerable Se espera que los métodos sean libres de efectos secundarios.

¿De verdad quieres "hacer algo" para cada elemento en el IEnumerable? Entonces foreach es la mejor opción La gente no se sorprende cuando ocurren los 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. Muy a menudo hay una consulta LINQ simple esperando ser descubierta acompañada de una respuesta de StackOverflow.com por Jon Skeet, Eric Lippert o Marc Gravell explicando cómo hacer lo que usted quiere.

Algunos ejemplos

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

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

Tal vez quieras crear un nuevo IEnumerable 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.


10
2017-12-26 22:56



Existe una versión experimental de Microsoft de Extensiones interactivas para LINQ (además en NuGet, ver El perfil de RxTeams para más enlaces). los Video del canal 9 lo explica bien.

Sus documentos solo se proporcionan en formato XML. He corrido esto documentación en Sandcastle para permitir que esté en un formato más legible. Descomprime el archivo de documentos y busca index.html.

Entre muchos otros regalos, proporciona la implementación ForEach esperada. 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
2017-08-15 15:49