sábado, 16 de mayo de 2015

.NET Framework. Método de extensión ForEach para IEnumerable

Desde la aparición de Linq muchos nos hemos hecho auténticos adictos de esta librería, hasta el punto de que si ahora nos la quitasen tendríamos que reinventarla.

Tareas que antes producían decenas de líneas de código difícil de entender y de mantener, de repente pasaban a poder implementarse en una sola instrucción, lo que redundaba en un incremento en la productividad, la claridad del código y su mantenibilidad.

Sin embargo, dentro de los métodos de extensión de la interfaz IEnumerable<T>, muchos hemos echado de menos un método que nos permita realizar una acción sobre todos los elementos de la colección: un método ForEach.

A decir verdad la implementación de un método ForEach para la interfaz IEnumerable<T> tiene sus defensores y sus detractores.

Hay quien argumenta que entre estos dos códigos:

    foreach (Producto item in Repuestos)
        CalcularTotal(item);

o

    Repuestos = Repuestos
        .ForEach(item => CalcularTotal(item));
    For Each item As Producto In Repuestos
        CalcularTotal(item)
    Next

o

    Repuestos = Repuestos _
        .ForEach(Sub(item) CalcularTotal(item))

resulta más legible el primero y, por tanto, el método de extensión no aporta nada (o al menos nada bueno). Y yo estoy absolutamente de acuerdo con ellos.

Sin embargo los métodos de extensión de Linq tienen otra gran ventaja: su capacidad para poder llamarse de manera encadenada.



Si elegimos otro ejemplo

    IEnumerable<Producto> Repuestos = Productos
        .Where(prod => prod.Familia == "Repuestos");

    foreach (Producto item in Repuestos)
        CalcularTotal(item);

    IEnumerable<Producto> MasVendidos = Repuestos
        .OrderByDescending(rep => rep.TotalVentas)
        .Take(5);

o

    IEnumerable<Producto> MasVendidos = Productos
        .Where(prod=>prod.Familia=="Repuestos")
        .ForEach(rep=>CalcularTotal(rep))
        .OrderByDescending(rep=>rep.TotalVentas)
        .Take(5);
    Dim Repuestos As IEnumerable(Of Producto) = Productos _
        .Where(Function(prod) prod.Familia = "Repuestos")

    For Each item As Producto In Repuestos
        CalcularTotal(item)
    Next

    Dim MasVendidos As IEnumerable(Of Producto) = Repuestos _
        .OrderByDescending(Function(rep) rep.TotalVentas) _
        .Take(5)

o


    Dim MasVendidos As IEnumerable(Of Producto) = Productos _
        .Where(Function(prod) prod.Familia = "Repuestos") _
        .ForEach(Sub(rep) CalcularTotal(rep)) _
        .OrderByDescending(Function(rep) rep.TotalVentas) _
        .Take(5)

podemos ver que el método de extensión ForEach sí que puede mejorar la legibilidad de nuestro código en algunos casos.

De hecho este método viene ya implementado en las clases System.Array o System.Collections.Generic.List<T>.

Para crear el método de extensión voy a crear una Clase estática IEnumerableExtension en C# o un Módulo con el mismo nombre en VB.NET. La Clase o el Módulo contendrá un único método genérico ForEach con dos parámetros:


  • El primer parámetro, como cualquier método de extensión, representa el objeto de la clase o interfaz que estamos extendiendo sobre el que se aplicará el método. En este caso una instancia de la interfaz IEnumerable<T>.
  • El segundo parámetro define la acción a aplicar a cada elemento de la enumeración y consistirá en un método con un único parámetro de tipo T.


    static class IEnumerableExtension
    {
        public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source
            , Action<T> action)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            foreach (T item in source)
                action(item);

            return source;
        }
    }
Module IEnumerableExtension

    <Extension()>
    Public Function ForEach(Of T)(source As IEnumerable(Of T) _
        , action As Action(Of T)) As IEnumerable(Of T)
        If action Is Nothing Then
            Throw New ArgumentException("action")
        End If

        For Each item As T In source
            action(item)
        Next

        Return source
    End Function

End Module

Y ya tenemos el método listo para usar en nuestro código. No olvides añadir la referencia al Namespace de la clase en el archivo de código donde desees utilizarlo mediante un using (en C#) o Imports (en VB.NET).


No hay comentarios:

Publicar un comentario