sábado, 16 de noviembre de 2013

Delegados - Delegates - parte 1



Los siguientes posts sobre Delegados que arrancamos con esta primera parte, pretenden ser algo más que una simple pasada sobre lo que nos ofrece esta clase del CLR y su uso en LinQ. Estos posts intentan profundizar dentro de su usabilidad en el Framework ya que considero que es una de las partes más difícil de comprender y de dominar, y su comprensión total, nos hace no tener ningún tipo de barrera en LinQ dentro de su parte más avanzada.


La definición más lógica que suele realizarse cuando hablamos de un delegado, es que es lo más parecido a un puntero a una función en C++. Esto no es del todo cierto, ya que un delegado es una clase y no un tipo








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



Un delegado es una clase del CLR, que tiene la capacidad de almacenar una o n instancias de un método(s), eso sí, los métodos a almacenar, tienen que cumplir una firma específica indicada en la firma del delegado.

Tenemos la siguiente definición de delegado:

public delegate string DelegadoAccionesString(string cadena);

Este delegado solo admite referencias a métodos que devuelvan un string y reciba un único parámetro de tipo string.

Este podría ser un método compatible para él.

public class MyClass
{
    public string EliminarDosUltimasLetras(string palabra)
    {
        return palabra.Remove(palabra.Count() - 2);
    }
}

Y podríamos realizar la asignación del mismo de la siguiente manera:

MyClass myClass = new MyClass();
DelegadoAccionesString miDelegadoAccionesString = myClass.EliminarDosUltimasLetras;

De esta forma ya tendríamos almacenada la referencia a este método y podríamos hacer con él lo que nos diera la gana: pasarlo por parámetros, ejecutarlo, etc.

Vale la pena destacar que en la asignación estamos añadiendo el nombre del método sin ‘()’ ni parámetros, y esto significa que lo que le estamos facilitando es la dirección de memoria del método, el AddressOf en Visual Basic.

Como podemos ver en su instanciación no vemos por ninguna parte el uso de la palabra reservada new, esto es así, porque el tipo Delegate, es un tipo inmutable, y esto significa que cada vez que modificamos algo de la referencia(s) a la(s) que apunta el objeto se reinstancia de nuevo.

Esta llamada sería completamente equivalente:

MyClass myClass = new MyClass();
DelegadoAccionesString miDelegadoAccionesString = new DelegadoAccionesString(myClass.EliminarDosUltimasLetras);

Esto nos puede recordar mucho al uso de la clase string y a las estructuras, ya que estas también son inmutables.

Para ejecutar el método almacenado en nuestro delegado, haremos lo siguiente:

string resultadoDelegado = miDelegadoAccionesString("Hola !!!");

Esta es una forma contraída de ejecución, la manera larga sería así:

string resultadoDelegado = miDelegadoAccionesString.Invoke("Hola !!!");



Propiedades y métodos más interesantes

Daremos una breve descripción de las propiedades y métodos más interesantes de la clase Delegate, con el fin de entender un poco mejor las entrañas de esta clase. Los iremos viendo con profundidad más adelante.

Propiedades
Solo tiene dos y son:

  • Method .- Propiedad de solo lectura que obtiene la descripción del método que contiene. En caso de guardar referencia a más de un método, este nos dará la información del último añadido. Es de tipo System.Reflection.MethodInfo.
  • Target .- Propiedad de tipo object de solo lectura que nos informa del propietario del método, para ser más exactos de la referencia propietaria. Al igual que con la propiedad Method, en caso de guardar más de un método y ser cada uno de referencias diferentes, este informará solo de la última añadida. Si los métodos referenciados son estáticos (static) está variable tomará el valor null

Métodos de instancia:
  • Clone .- La clase Delegate implementa la interfaz ICloneable, por lo que tiene la capacidad de poder clonarse.
  • GetInvocationList .- Devuelve el conjunto de objetos de tipo Delegate correspondientes a los métodos que apunta.
  • Invoke .- Llama a la ejecución del método(s) al/los que apunta.
  • BeginInvoke y EndInvoke .- Realiza llamadas asíncronas de los métodos.

Métodos Estáticos:
  • Remove .- Elimina la última referencia a método añadida.
  • RemoveAll .- Elimina todas las referencias que contiene el objeto facilitado por parámetros.
  • Conbine .- Combina/añade referencias de uno o varios delegados.


Como veremos con Multicast Delegate, estos métodos casi no tienen uso, ya que se han facilitado su uso con sobrecargas de operadores.




Aumentando el dinamismo y la versatilidad de nuestros métodos

Los delegados nos permiten una versatilidad muy grande, dando a nuestros métodos y a nuestras clases una dimensión mucho más extensa, ampliando la capacidad de sus argumentos con acciones y no solo con valores o referencias.

Vamos a ver un ejemplo, que intenta demostrar la parte práctica de los delegados. Debemos verlo como método didáctico, ya que según vayamos avanzando, nos daremos cuenta que con la inclusión de métodos anónimos, expresiones lambda y operadores de consulta, el dinamismo aumenta de manera estratosférica.

El ejemplo consta de una clase inicial llamada AccionesConEnteros, que intenta emular una clase con acciones cotidianas de enteros, pero llevadas a la finalidad de transformar una colección de enteros en una colección de strings directamente salida del horno para ser impresa. Como veremos esta colección de enteros puede sufrir una serie de variaciones en su salida según nuestro gusto.

Lo primero importante a resaltar en la clase (mejor dicho en sus namespace) es la definición de dos delegados:

public delegate bool   DelegadoComparacion(int valor); 
public delegate string DelegadoAccion     (string valor);

La firma de estos dos delegados es la coincidente para una serie de métodos de la clase y lo que es más importante servirán de tipo de parámetro para el método principal.

Ahora vamos a ver el método principal de la clase:

public List<string> TransformarListaValoresParaImpresion(List<int> listaValores, DelegadoComparacion comparacion,
                                                            DelegadoAccion accion, string mensajeAccion)

El método principal TransformarListaValoresParaImpresion, toma 4 parámetros:

  1. listaValores .- De tipo List<int>, representa la lista de valores enteros que serán comprobados y trasformados en strings para su impresión.
  2. comparación .- De tipo DelegadoComparacion, representa el método que realizará la comparación de los valores enteros.
  3. acción .- De tipo DelegadoAccion representa la acción de sustitución que se realizará al transformar el tipo int en string.
  4. mensajeAccion .- De tipo string, muestra el mensaje que se podrá acoplar en el proceso de transformación de int a string.

Continuando con la clase, vemos que posee 2 grupos de métodos, que atesoran 2 métodos cada uno y que concuerdan con cada una de las firmas de los delegados iniciales, éstos podrán ser utilizados para realizar la comparación o para realizar la acción de transformación:

#region Comparaciones 
 
public bool EsNegativo(int cifra)

public bool EsPar(int cifra)

/// Podríamos tener n métodos con la firma  bool (int)
 
#endregion
 
#region Acciones de Sustitución
 
public string SustituirMensajeConFechaPorValor(string mensaje)

public string SustituirMensajeConFechaPorValorMasError(string mensaje)

/// Podríamos tener n métodos con la firma  string (string)
        
#endregion

Importante resaltar que aquí se podrían añadir todos los métodos que creyéramos necesarios y sin limitaciones, la única restricciones que tendrían que cumplir con la firma de sus delegados, para luego ser usados como parámetros en el método principal TransformarListaValoresParaImpresion.

Añado el código completo de la clase:

public delegate bool   DelegadoComparacion(int valor); 
public delegate string DelegadoAccion     (string valor);
 
public class AccionesConEnteros
{
    public List<string> TransformarListaValoresParaImpresion(List<int> listaValores, DelegadoComparacion comparacion,
                                                                DelegadoAccion accion, string mensajeAccion)
    {
        List<string> resultado = new List<string>();
 
        foreach (var valor in listaValores)
        {
            if (comparacion(valor))
                resultado.Add(accion(mensajeAccion));
            else
                resultado.Add(valor.ToString());
        }
 
        return resultado;
    }
 
 
    #region Comparaciones 
 
    public bool EsNegativo(int cifra)
    {
        return cifra < 0;
    }
 
    public bool EsPar(int cifra)
    {
        return cifra % 2 == 0;
    }
 
    /// Podríamos tener n métodos con la firma  bool (int)
 
    #endregion
 
    #region Acciones de Sustitución
 
    public string SustituirMensajeConFechaPorValor(string mensaje)
    {
        return string.Format("{0} - {1}", mensaje, DateTime.Now);
    }
 
    public string SustituirMensajeConFechaPorValorMasError(string mensaje)
    {
        return string.Format("{0} - {1} - {2}", mensaje, DateTime.Now, "ERROR");
    }
 
    /// Podríamos tener n métodos con la firma  string (string)
        
    #endregion
 
}

Ahora añadiremos un ejemplo de uso:

static void Main(string[] args)
{
    AccionesConEnteros accInt = new AccionesConEnteros();
 
    List<int> numeros = new List<int>() {-9, -8, -7, -6, -5, -4, 1, 3, 5};
 
    var numerosParaImprimir 
        = accInt.TransformarListaValoresParaImpresion
        (numeros, 
            accInt.EsNegativo, // podríamos elegir cualquier método bool(int)
            accInt.SustituirMensajeConFechaPorValorMasError, // podríamos elegir cualquier método string(string)
            "No se admiten negativos");
 
    foreach (var num in numerosParaImprimir)
    {
        Console.WriteLine(num);
    }
 
    Console.Read();
}

Con el siguiente resultado:















Como podemos ver en los comentarios, estamos haciendo uso de los métodos de comparación y acción que nos brinda la clase AccionesConEnteros, pero podríamos crearnos los nuestros propios (cumpliendo la firma claro) dentro de la clase donde se realiza la ejecución y serían completamente válidos.

Lo hemos hecho:

class Program
{
    static void Main(string[] args)
    {
        AccionesConEnteros accInt = new AccionesConEnteros();
 
        List<int> numeros = new List<int>() {-9, -8, -7, -6, -5, -4, 1, 3, 5};
 
        var numerosParaImprimir 
            = accInt.TransformarListaValoresParaImpresion
            (numeros, 
                EsImparNegativo, // podríamos elegir cualquier método bool(int)
                SustituirMensajeConRisa, // podríamos elegir cualquier método string(string)
                "No se admiten negativos impares");
 
        foreach (var num in numerosParaImprimir)
        {
            Console.WriteLine(num);
        }
 
        Console.Read();
    }
 
 
    public static bool EsImparNegativo(int numero)
    {
        return numero < 0 && numero % 2 != 0;
    }
 
    public static string SustituirMensajeConRisa(string mensaje)
    {
        return string.Format("{0} ----- {1}", mensaje, "JAJAJAJAAJAJAA");
    }
 
}


Hemos añadido dos métodos nuevos y los hemos sustituido en la llamada al método de trasformación, con el siguiente resultado:















Y hasta aquí la primera parte sobre delegados, en el siguiente post entraremos más en materia sobre los delegados genéricos.