jueves, 23 de enero de 2014

Delegados - Delegates - parte 2



Continuamos con la segunda parte de nuestros queridos delegados, esa clase tan útil, y utilizada por el framwork y tantas veces se nos hace tan complicada de entender.

En esta segunda parte nos centraremos principalmente en estudiar la clase base y los métodos y propiedades más importantes de ésta, que hacen que el delegado realice una series de funciones muy marcadas y en ocasiones totalmente transparentes para nuestro trabajo diario con ellos, ya que están ocultas dentro de la redefinición de operadores.


La segunda parte del post irá dirigida al uso de Generics con delegados. En ella profundizaremos en la importancia y la potencia que tienen para la ejecución de referencias a funciones y a métodos genéricos. También daremos un repaso a los delegados genéricos más importantes del Framework y de LinQ en general que no son otros que Func<T> y Action<T>.





Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.



Multicast Delegates

Cuando realizamos una instanciación de un delegado, indirectamente estás declarando una clase que deriva de System.MulticastDelegate.

La clase MulticastDelegate, proporciona la capacidad de guardar una lista de métodos, en vez de una única referencia a un método, y proporciona una serie de sobrecargas de operadores a la hora de interactuar con esta lista de métodos.

Como vimos anteriormente en la parte 1, la clase Delegate poseía una serie de métodos estáticos para realizar acciones sobre las referencias almacenadas en los delegados, sobre estos métodos se basan las sobrecargas de los operadores:
  • Convine .- Combina métodos referenciados, y su uso es igual que el del operador +=.
  • Remove .- Elimina métodos referenciados, y su uso es similar al operador -=, y digo es similar, ya que el método Remove elimina la última referencia añadida y el uso del operador -= elimina la referencia facilitada después del operador.
Resaltar que cuando tenemos un delegado con una lista de referencias a métodos almacenada con una firma con un tipo de devolución diferente a void, ósea que devuelve un valor, a la hora de realizar la ejecución, se lanzarán todos y cada uno de los métodos que contiene, pero el resultado devuelto será solo el de la última referencia añadida.

Ejemplo:

public delegate int DevolverDelegate();
 
class Program
{
        
 
    static void Main(string[] args)
    {
        DevolverDelegate miDevolverDelegate = new ClaseA().DevolverUno;
        miDevolverDelegate += new ClaseB().DevolverDos;
 
        int resultado = miDevolverDelegate();
    }
}

La variable resultado contendrá el valor de retorno de ejecutar ClaseB.DevolverDos();

Esto opera exactamente igual a hora de utilizar las Propiedades Target y Method.



Delegados Genéricos

Ahora llega la hora de ir atando cabos, y de ir aglutinando conocimientos, en este caso, vamos a aplicar muchas de las cosas que vimos con Generics con el uso de delgados.

Vamos a ver un ejemplo muy sencillo de su uso, que constará de un método TransformadorDatos, que realizará una modificación que será facilitada por parámetro mediante un tipo Delegate<T> en una colección de datos de tipo T también facilitada por parámetros. Veamos el método:

public delegate T ModificarDelegate<T>(T item);
 
public class TransformadorGenerico
{
    public void TransformarDatos<T>(List<T> coleccion, ModificarDelegate<T> delegadoModificador)
    {
        for(int i = 0; i < coleccion.Count; i++)
        {
            // Realizamos la modificación
            coleccion[i] = delegadoModificador(coleccion[i]);
        }   
    }
 
    // ***************************************************************************************
    //   Recordar que son métodos de prueba, valdrían cualquiera que cumpliera con la firma
    // ***************************************************************************************
 
    #region Métodos Compatibles
 
    public DateTime SumarFecha1Año(DateTime fecha)
    {
        return fecha.AddYears(1);
    }
 
    public string TrimToUpper(string dato)
    {
        return dato.Trim().ToUpper();
    }
 
    public double DividirEntreDos(double cifra)
    {
        return cifra / 2;
    }
    
    #endregion
}

El código consumidor sería el siguiente:

class Program
{
 
    static void Main(string[] args)
    {
        TransformadorGenerico tdg = new TransformadorGenerico();
 
        List<DateTime> colecDateTime = new List<DateTime>()
            { DateTime.Today, DateTime.Today.AddDays(1), DateTime.Today.AddDays(2) };
        List<string> colecString = new List<string>() { "hola  ", "adiós   ", "nos vemos !!!   " };
        List<double> colecDouble = new List<double>() { 2, 4, 8.5 };
 
        tdg.TransformarDatos(colecDateTime, new ModificarDelegate<DateTime>(tdg.SumarFecha1Año));
        ImprimirValores(colecDateTime);
        tdg.TransformarDatos(colecString, new ModificarDelegate<string>(tdg.TrimToUpper));
        ImprimirValores(colecString);
        tdg.TransformarDatos(colecDouble, new ModificarDelegate<double>(tdg.DividirEntreDos));
        ImprimirValores(colecDouble);
 
        Console.Read();
    }
 
 
    static void ImprimirValores<T>(List<T> valores)
    {
        foreach (var valor in valores)
        {
            Console.WriteLine(valor);
        }
    }
}


Resultado:















Func<> y Action<>

Func<T> y Action<T>, son dos delegados genéricos situados en el namespace System.

Estos delegados genéricos definidos dentro del Framework, tratan de facilitarnos las cosas a la hora de la fabricación de nuestros delegados, ya que ellos cubren por si solos el 99% de las definiciones posibles.
Empecemos con Action<T>.

Action<T> como su propio nombre indica, define una acción, lo que significa que no devuelve ningún tipo de valor. Vamos a ver sus definiciones posibles:

delegate void Action            ();
delegate void Action<T1>        (T1 valor1);
delegate void Action<T1, T2>    (T1 valor1, T2 valor2);
delegate void Action<T1, T2, T3>(T1 valor1, T2 valor2, T3 valor3);
 
/// Así hasta 16 posibles parametros / valores

Así que si necesitamos crear un delegado que pueda almacenar acciones para dos int y un string, de este tipo:

public delegate void EjemploDelegado(int cifra1, int cifra2, string cadena);

Ya no sería necesario establecer esta definición de Delegate, ya que con Action<> podríamos cubrirlo:

Action<int, int, string> miVariableAction;

Para el tipo Func<>, pues exactamente igual a Action<> con la diferencia de que Func<> siempre tiene que devolver un valor, y este corresponderá con su primer parámetro de tipo, veamos sus definiciones:

delegate TResult Func<out TResult>                     ();
delegate TResult Func<in T1, out TResult>              (T1 valor1);
delegate TResult Func<in T1, in T2, out TResult>       (T1 valor1, T2 valor2);
delegate TResult Func<in T1, in T2, in T3, out TResult>(T1 valor1, T2 valor2, T3 valor3);
 
///  Así hasta 16 posibles parametros de valores

Las anotaciones in y out dentro de la definición de los parámetros de tipos, son correspondientes a la varianza y covarianza.


Vamos a ver otra transformación de definición de Delegate en delegado genérico, ahora en Func<>:

public delegate bool   DelegadoComparacion(int valor, int valor2); 

Igualmente, no haría falta realizar la definición de manera explícita, ya que con Func<> podríamos cubrirla así:

Func<int, int, bool> miVariableFunc;

Si echamos la vista a la sección anterior y vemos la firma del método genérico principal:

public void TransformarDatos<T>(List<T> coleccion, ModificarDelegate<T> delegadoModificador)

No tendríamos que haber gastado tiempo en crear la definición de ModificarDelegate, ya que podríamos haberlo sustituido por un Func<>:

public void TransformarDatos<T>(List<T> coleccion, Func<T,T> delegadoModificador)



Predicate<T>

En el CLR, también nos podemos encontrar un delegado genérico llamado Predicate<T>. Este delegado existe como parámetro en métodos como List<T>.Find(Predicate<T>) y se conserva porque es anterior a la salida de Func<T> dentro de la versión 2.0 del Framework. La definición de Predicate<T> es la siguiente:

delegate bool Predicate<T>(T valor);

Por lo que su función queda completamente cubierta con una de las definiciones de Func<>:

Func<T, bool> variablePredicate;

Supongo que seguirá usándose por razones de compatibilidad.

Es muy importante señalar que el tipo Func<> es importantísimo para LinQ, ya que un número elevadísimo de sus métodos extensores utilizan parámetros de este tipo.