¿Por qué no hay un método de extensión ForEach en IEnumerable?

Inspirado por otra pregunta que pregunta sobre la función Zip falta:

¿Por qué no hay ForEach método de extensión ForEach en la clase Enumerable ? ¿O en cualquier lado? La única clase que obtiene un método ForEach es List . ¿Hay alguna razón por la que falta (rendimiento)?

Ya hay una statement foreach incluida en el idioma que hace el trabajo la mayor parte del tiempo.

Odiaría ver lo siguiente:

 list.ForEach( item => { item.DoSomething(); } ); 

En lugar de:

 foreach(Item item in list) { item.DoSomething(); } 

Este último es más claro y fácil de leer en la mayoría de las situaciones , aunque tal vez un poco más largo para escribir.

Sin embargo, debo admitir que cambié mi postura sobre ese tema; un método de extensión ForEach () sería de hecho útil en algunas situaciones.

Estas son las principales diferencias entre el enunciado y el método:

  • Verificación de tipos: foreach se realiza en tiempo de ejecución, ForEach () está en tiempo de comstackción (¡Big Plus!)
  • La syntax para llamar a un delegado es de hecho mucho más simple: objects.ForEach (DoSomething);
  • ForEach () podría estar encadenado: aunque la maldad / utilidad de dicha característica está abierta a discusión.

Esos son todos los puntos importantes hechos por muchas personas aquí y puedo ver por qué las personas están perdiendo la función. No me importaría que Microsoft agregue un método ForEach estándar en la próxima iteración marco.

ParaCada método fue agregado antes de LINQ. Si agrega la extensión ForEach, nunca se invocará a las instancias de la lista debido a las restricciones de los métodos de extensión. Creo que la razón por la que no se agregó es no interferir con la existente.

Sin embargo, si realmente extrañas esta pequeña y agradable función, puedes lanzar tu propia versión

 public static void ForEach( this IEnumerable source, Action action) { foreach (T element in source) action(element); } 

Puedes escribir este método de extensión:

 // Possibly call this "Do" IEnumerable Apply (this IEnumerable source, Action action) { foreach (var e in source) { action(e); yield return e; } } 

Pros

Permite el encadenamiento:

 MySequence .Apply(...) .Apply(...) .Apply(...); 

Contras

En realidad, no hará nada hasta que haga algo para forzar la iteración. Por esa razón, no debería llamarse .ForEach() . Puede escribir .ToList() al final, o puede escribir este método de extensión también:

 // possibly call this "Realize" IEnumerable Done (this IEnumerable source) { foreach (var e in source) { // do nothing ; } return source; } 

Esto puede ser una desviación muy significativa de las bibliotecas de envío C #; los lectores que no estén familiarizados con sus métodos de extensión no sabrán qué hacer con su código.

La discusión aquí da la respuesta:

En realidad, la discusión específica que presencié en realidad dependía de la pureza funcional. En una expresión, con frecuencia se hacen suposiciones acerca de no tener efectos secundarios. Tener ForEach es específicamente invitar a los efectos secundarios en lugar de solo aguantarlos. – Keith Farmer (Socio)

Básicamente, se tomó la decisión de mantener los métodos de extensión funcionalmente “puros”. Un ForEach alentaría los efectos colaterales cuando se usan los métodos de extensión Enumerable, que no era la intención.

Si bien estoy de acuerdo en que es mejor usar el constructo foreach integrado en la mayoría de los casos, creo que el uso de esta variación en la extensión ForEach <> es un poco mejor que tener que administrar el índice en un foreach habitual:

 public static int ForEach(this IEnumerable list, Action action) { if (action == null) throw new ArgumentNullException("action"); var index = 0; foreach (var elem in list) action(index++, elem); return index; } 

Ejemplo

 var people = new[] { "Moe", "Curly", "Larry" }; people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p)); 

Te daría:

 Person #0 is Moe Person #1 is Curly Person #2 is Larry 

Una solución consiste en escribir .ToList().ForEach(x => ...) .

pros

Fácil de entender: el lector solo necesita saber qué se envía con C #, no ningún método de extensión adicional.

El ruido sintáctico es muy leve (solo agrega un pequeño código extraño).

Por lo general, no cuesta más memoria, ya que un nativo .ForEach() tendría que realizar toda la colección, de todos modos.

contras

El orden de las operaciones no es ideal. Preferiría darme cuenta de un elemento, luego actuar sobre él y luego repetirlo. Este código realiza primero todos los elementos y luego actúa sobre ellos en secuencia.

Si darse cuenta de que la lista arroja una excepción, nunca podrá actuar en un solo elemento.

Si la enumeración es infinita (como los números naturales), no tiene suerte.

Siempre me he preguntado eso, es por eso que siempre llevo esto conmigo:

 public static void ForEach(this IEnumerable col, Action action) { if (action == null) { throw new ArgumentNullException("action"); } foreach (var item in col) { action(item); } } 

Buen método de extensión.

Así que ha habido muchos comentarios sobre el hecho de que un método de extensión ForEach no es apropiado porque no devuelve un valor como los métodos de extensión LINQ. Si bien esta es una statement de hechos, no es del todo cierto.

Todos los métodos de extensión LINQ devuelven un valor para que puedan encadenarse entre sí:

 collection.Where(i => i.Name = "hello").Select(i => i.FullName); 

Sin embargo, solo porque LINQ se implemente utilizando métodos de extensión no significa que los métodos de extensión se deben usar de la misma manera y devolver un valor. Escribir un método de extensión para exponer una funcionalidad común que no devuelve un valor es un uso perfectamente válido.

La argumentación específica sobre ForEach es que, en función de las limitaciones de los métodos de extensión (es decir, que un método de extensión nunca anulará un método heredado con la misma firma ), puede haber una situación en la que el método de extensión personalizado esté disponible en todas las clases. IEnumerable > excepto List >. Esto puede causar confusión cuando los métodos comienzan a comportarse de manera diferente según se llame o no al método de extensión o al método heredado.

Podrías usar el Select (encadenable, pero evaluado con pereza), primero haciendo tu operación, y luego regresando la identidad (o algo más si lo prefieres)

 IEnumerable people = new List(){"alica", "bob", "john", "pete"}; people.Select(p => { Console.WriteLine(p); return p}); 

Tendrá que asegurarse de que todavía se evalúa, ya sea con Count() (la operación más económica para enumerar afaik) u otra operación que necesita de todos modos.

Sin embargo, me encantaría verlo traducido a la biblioteca estándar:

 static IEnumerable WithLazySideEffect(this IEnumerable src, Action action) { return src.Select(i => { action(i); return i} ); } 

El código anterior se convierte entonces en people.WithLazySideEffect(p => Console.WriteLine(p)) que es efectivamente equivalente a foreach, pero vago y encadenable.

@ Coinin

El poder real del método de extensión foreach implica la reutilización de la Action<> sin agregar métodos innecesarios a su código. Supongamos que tiene 10 listas y desea realizar la misma lógica en ellas, y una función correspondiente no cabe en su clase y no se reutiliza. En lugar de tener diez bucles for, o una función genérica que obviamente es un ayudante que no pertenece, puede mantener toda su lógica en un solo lugar (la Action<> . Entonces, docenas de líneas se reemplazan con

 Action f = { foo }; List1.ForEach(p => f(p)) List2.ForEach(p => f(p)) 

etc …

La lógica está en un lugar y no has contaminado tu clase.

En 3.5, todos los métodos de extensión agregados a IEnumerable están ahí para soporte de LINQ (observe que están definidos en la clase System.Linq.Enumerable). En esta publicación, explico por qué foreach no pertenece a LINQ: método de extensión LINQ existente similar a Parallel.For?

Tenga en cuenta que MoreLINQ NuGet proporciona el método de extensión ForEach que está buscando (así como también un método Pipe que ejecuta el delegado y arroja su resultado). Ver:

La mayoría de los métodos de extensión LINQ arrojan resultados. Porque cada uno no encaja en este patrón ya que no devuelve nada.

Puedes usar seleccionar cuando quieras devolver algo. Si no lo hace, puede usar ToList primero, porque probablemente no quiera modificar nada en la colección.

Escribí una publicación en el blog sobre esto: http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx

Puede votar aquí si desea ver este método en .NET 4.0: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=279093

¿Soy yo o la lista .Foreach ha quedado obsoleta por Linq? Originalmente había

 foreach(X x in Y) 

donde Y simplemente tenía que ser IEnumerable (Pre 2.0) e implementar GetEnumerator (). Si miras el MSIL generado, puedes ver que es exactamente lo mismo que

 IEnumerator enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { int i = enumerator.Current; Console.WriteLine(i); } 

(Ver http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx para MSIL)

Luego en DotNet2.0 llegaron los generics y la lista. Foreach siempre me ha parecido una implementación del patrón Vistor (ver Patrones de diseño de Gamma, Helm, Johnson, Vlissides).

Ahora, por supuesto, en 3.5 podemos usar un Lambda para el mismo efecto, por ejemplo, intente http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh- mi/

Para usar Foreach, su lista debe cargarse en la memoria primaria. Pero debido a la naturaleza de carga lenta de IEnumerable, no proporcionan ForEach en IEnumerable.

Sin embargo, mediante la carga ansiosa (utilizando ToList ()), puede cargar su lista en la memoria y aprovechar ForEach.

Si tiene F # (que estará en la próxima versión de .NET), puede usar

Seq.iter doSomething myIEnumerable

Nadie ha señalado aún que ForEach da como resultado la comprobación del tipo de tiempo de comstackción, donde la palabra clave foreach está activada en tiempo de ejecución.

Después de haber hecho algunas refactorizaciones donde se usaron ambos métodos en el código, estoy a favor de .ForEach, ya que tuve que buscar fallas de prueba / fallas en el tiempo de ejecución para encontrar los problemas foreach.

Me gustaría expandir la respuesta de Aku .

Si desea llamar a un método con el único propósito de su efecto secundario sin iterar todo el enumerable primero, puede usar esto:

 private static IEnumerable ForEach(IEnumerable xs, Action f) { foreach (var x in xs) { f(x); yield return x; } }