Entendiendo los delegados Func<> y Action<>

Entendiendo los delegados Func<> y Action<>

Hola a todos, hoy les traigo este post tratando de desenredar la madeja de los delegados Action<T> y Func<T>.

Empecemos con Action<> y Func<>

Estas herramientas son extremadamente útiles a la hora de reducir código duplicado, pero para aplicarlas con sabiduría debemos comprenderlas, veamos cómo.

Action<>

Empecemos por la definición literal de MSDN.

Action<T> Delegate

Encapsulates a method that has a single parameter and does not return a value.

Referencia: https://msdn.microsoft.com/en-us/library/018hxwa8(v=vs.110).aspx

Un delegado que encapsula un método que tienen un solo parámetro y no retorna un valor.

Analicemos la parte simple de la oración, "un método que tiene un solo parámetro y no retorna un valor". Es decir, un método void.

void Metodo(T parametro){
    //hago algo
}

Ahora, la parte más complicada, "es un delegado".

¿Qué es un delegado?

Otra vez, veamos que nos dice la MSDN.

A delegate is a reference type that can be used to encapsulate a named or an anonymous method. Delegates are similar to function pointers in C++; however, delegates are type-safe and secure.

Referencia: https://msdn.microsoft.com/en-us/library/900fyy8e.aspx

Un delegado es un tipo por referencia que puede ser usado para encapsular un método con nombre o anónimo. Los delegados son similares a punteros a funciones en c++, sin embargo, los delegados tienen seguridad de tipos.

Veamos el ejemplo de código de la MSDN que es bastante ilustrativo.

    
    // Declare delegate -- defines required signature:
    delegate double MathAction(double num);

    class DelegateTest
    {
        // Regular method that matches signature:
        static double Double(double input)
        {
            return input * 2;
        }

        static void Main()
        {
            // Instantiate delegate with named method:
            MathAction ma = Double;

            // Invoke delegate ma:
            double multByTwo = ma(4.5);
            Console.WriteLine("multByTwo: {0}", multByTwo);

            // Instantiate delegate with anonymous method:
            MathAction ma2 = delegate(double input)
            {
                return input * input;
            };

Como podemos ver, al definir un delegado estamos creando un tipo, en el ejemplo anterior MathAction, el cual define la firma del método que podemos almacenar en una variable del tipo del delegado, es decir en una variable del tipo MathAction(para el ejemplo anterior).

Cabe resaltar, que no solo podemos almacenar métodos con nombre, también podríamos hacerlo con métodos anónimos, como se muestra en el ejemplo.

MathAction ma2 = delegate(double input)
{
  return input * input;
};

Poniendo todo junto...

Action<T>, es un delegado genérico que encapsula un método que posee un solo parámetro y no retorna un valor. Es decir, un Action<T> nos permite crear un delegado sin tener que declarar el tipo.

delegate void PrintResult(double num);

PrintResult operation = EjemploMetodo;

void EjemploMetodo(double num){
 Console.WriteLine("El resultado es:{0}",num);
}

En cambio con un action, no necesitas crear un tipo, porque el tipo es Action<T>:

Action<double> op = EjemploMetodo;

Ahora, si quisiéramos hacerlo más corto, podríamos usar una expresión lambda (más adelante explicaremos con detalle de que se trata):

delegate void PrintResult(double num);

PrintResult operation = num => Console.WriteLine("El resultado es:{0}",num);

Y en el caso de las Action<T>:

Action<double> operation = num => Console.WriteLine("El resultado es:{0}",num);

Y es el turno de las Func<T>

Una Func es muy parecida a una action<T>, de hecho ambos son delegados genéricos, la diferencia es que en el caso de la Func<T>, esta devuelve un valor.

Func<T>, es un delegado que encapsula un método sin parámetros que devuelve un valor del tipo T.

Veámoslo en un ejemplo:

Func funcionFecha = ()=> DateTime.Now;

//Luego podemos usar nuestra variable para invocar el método

Console.WriteLine(funcionFecha());

¿Cuantos parámetros como máximo puede tener un Action<> o Func<>?

Hasta ahora hemos visto delegados Action<> de un parámetro y Func<> sin parámetros, pero es posible incluir tantos parámetros como necesitemos.

Delegado Equivalente Delegado Equivalente
Action<T> void method(T param1) Func<T> T function( )
Action<T,A> void method(T param1,A param2) Func<T,A> T function(A param1 )
Action<T,A,B> void method(T param1,A param2,B param3) Func<T,A,B> T function(A param1, B param2 )
... ... ... ...

Por ejemplo, si quisiéramos guardar la referencia de un método que recibe dos parámetros, una cadena y una fecha pero no devuelve ningún valor, nuestro delegado seria así por ejemplo:

Action<string,DateTime> metodo = (x,y) => Console.WriteLine(x);

Sin embargo, si quisiéramos guardar la referencia a una operación aritmética:

Func<double,int,int> operacion = (x,y) => return x/y;

Como puedes ver, en el caso del delegado Func<>, el primer tipo que especifiquemos será el tipo de devolución de la función y los subsiguientes serán los tipos de los parámetros.

Y dentro de todo esto, que son las expresiones Lambda?
Como puedo refactorizar mi código usando Action<> y Func<>?

Veremos todo esto en la segunda parte de este post ;)