sábado, 31 de mayo de 2014

Delegados en C# - Parte 5: Delegados Func y Action de .NET Framework

Tabla de Contenido

0. Introducción
1. Delegados Genéricos: Action
1.1 Delegado Action
1.2 Delegado Action<T>
2. Delegados Genéricos: Func
2.1 Delgado Func<TResult>
3. Conclusiones
4. Glosario
5. Literatura & Enlaces

0. Introducción

En este último artículo de la serie de delegados en C#, pasaremos a explorar los delegados predefinidos en Microsoft .NET Framework: Func y Action. Veremos la utilidad de estos delegados incorporados para promover la reutilización propuesta en la biblioteca de clases del Framework. Introduciré varios ejemplos de contraste entre definir nuestros propios delegados y usar los predefinidos.

1. Delegados Genéricos: Action

Ya hemos tenido la oportunidad de hablar acerca de los tipos genéricos en C#, más precisamente acerca de los delegados genéricos (Tipos Genéricos en C# - Parte 8: Delegados Genéricos), lo que tenemos que hacer ahora es comprender los delegados que incorpora Microsoft .NET Framework y que nos pueda hacer la tarea de confección de código mucho más amena.

Continuando, empecemos por explorar algunos de estos delegados genéricos:

1.1 Delegado Action

Empecemos por conocer su firma [3]:

public delegate void Action()

Su especificación consiste de cero parámetros, y su tipo de retorno void. Entonces, ahora pasemos a explorar a través de ejemplos su utilidad. En primera instancia cabe decir que el uso de este delegado nos va a ahorrar la escritura de un delegado personalizado, como:

public delegate void Delegado();

Nota desde [3]:
Alternativa a Action() con tipo de retorno
Figura 1. Alternativa a Action() con tipo de retorno.

Respecto a lo anterior, la version Action() posee su contraparte con tipo de retorno genérico: Func<TResult> [7].

Archivo de código fuente SinAction.cs [enlace alternativo]:

En la línea 7 se define explícitamente el delegado MostrarValor, más específicamente:

public delegate void MostrarValor();

Y lo usamos en la invocación indirecta en la línea 35.

[Nota: Lo anterior puede resultar muy sencillo para quienes ya hayan leído las 4 entregas anteriores de esta serie de artículos de «Delegados en C#».]

Ahora si utilizamos el delegado genérico predefinido Action:

Archivo de código fuente ConAction.cs [enlace alternativo]:

A destacar la línea 34, en donde usamos el delegado Action para declarar una instancia de este tipo de delegado y asignarle la referencia al método MostarEnVentana.

Si comparamos este código con el del archivo de código fuente SinAction.cs, nos hemos deshecho de la declaración del delegado MostrarValor, dado que la firma del delegado incorporado Action es la misma, y lo podemos reutilizar. Esto brinda un grado de abstracción extra en la tarea de codificación.

1.1.1 Action con método anónimos

La línea 34 la pudimos haber reemplazo de la siguiente manera (usando un método anónimo -cfr. Delegados en C# - Parte 2: Métodos Anónimos-):

Action del = delegate()
{
nombre.MostrarEnVentana();
}

O sino, con una expresión lambda:

Action del = () => nombre.MostrarEnVentana();

1.2 Delegado Action<T>

Tenemos otro delegado genérico que posee la siguiente firma [6]:

public delegate void Action<in T>(T obj)

Su uso es análogo al de la sección 1.1, sin embargo incluyo este ejemplo:

Archivo de código fuente SinActionT.cs [enlace alternativo]:

En línea 6 se declara el delegado MostrarMensaje, con la siguiente especificación:
  • Tipo retorno: void
  • Parámetros: string
El asignamos uno de los métodos (i.e., MostrarEnVentana, o Console.WriteLine) dependiendo de la evaluación:

if (Environment.GetCommandLineArgs().Length > 1)

Ahora, utilicemos la versión de demostración con Action<T>:

Archivo de código fuente ConActionT.cs [enlace alternativo]:

En la línea 10 ocurre la siguiente acción:

Action<string> mm;

es la declaración de un delegado genérico con tipo string. Ocurre la misma evaluación que en el ejemplo anterior (esta vez en la línea 12).

> Prueba de ejecución:

1.3 Otros delegados genéricos

Microsoft .NET Framework cuenta otros 15 delegados genéricos, partiendo con Action() hasta Action(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) [8].

[Nota: Para saber más, recomiendo leer la documentación de MSDN.]

2. Delegados Genéricos: Func

Es la oportunidad de hablar los delegados genéricos incorporados en Microsoft .NET Framework: Func; que a diferencia de los delegados genéricos Action, estos sí retornan un tipo genérico.

2.1 Delegado Func<TResult>

Este es delegado genérico es la alternativa al que exploramos en la sección 1.2, ya que posee tipo de retorno. Conozcamos su firma [9]:

public delegate TResult Func<out TResult>()

Descripción puntual:

2.1.1 Ejemplo sin Func<TResult>

Creemos un ejemplo sin el uso del delegado genérico Func<T>:

Archivo de código fuente SinFunc.cs [enlace alternativo]:

En la línea 6 creamos el delegado Escribir con la siguiente firma:

public delegate bool Escribir();

Tengamos en cuenta que su tipo de retorno, es bool. En la siguiente sub-sección vamos a aprovechar para usar el delegado genérico integrado al que le podemos especificar ese tipo de retorno.

2.2.2 Ejemplo con Func<TResult>

Archivo de código fuente ConFunc.cs [enlace alternativo]:

Observemos que en la línea 12 creamos una instancia de Func con el tipo específico bool. Gracias al grado de genericidad de Func, podemos asignar la referencia al método EnviarAArchivo de la clase SalidaContenido.

2.2 Otros delegados genéricos Func

En el namespace System podemos encontrar el resto de delegados genéricos integrados Func:
  • Func(T, TResult)
  • Func(T1, T2, TResult)
  • Func(T1, T2, T3, TResult)
  • ...
  • Func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult)
[Nota: Para saber más, recomiendo leer la documentación de MSDN.]

3. Conclusiones

Conocimos los delegados genéricos de Microsoft .NET Framework, con los cuales nos podemos ahorrar la escritura de una cantidad de código. Exploramos los delegados genéricos Action, el cual posee hasta 17 versiones distintas (difieren en el número de parámetros); y los delegados genéricos Func corresponde con delegados con tipo de retorno, y parámetros, opuestos a los primeros (i.e., Action).

4. Glosario

  • Action
  • Delegado
  • Delegado genérico
  • Expresión lambda
  • Método anónimo
  • Tipo genérico

5. Literatura & Enlaces

[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: C#/.NET Little Wonders: The Generic Action Delegates - http://geekswithblogs.net/BlackRabbitCoder/archive/2011/11/03/c.net-little-wonders-the-generic-action-delegates.aspx
[3]: Action Delegate (System) - http://msdn.microsoft.com/en-us/library/system.action(v=vs.110).aspx
[4]: Tipos Genéricos en C# - Parte 8: Delegados Genéricos - http://ortizol.blogspot.com/2014/05/tipos-genericos-en-c-parte-8-delegados.html
[5]: Delegados en C# - Parte 2: Métodos Anónimos | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/05/delegados-en-csharp-parte-2-metodos-anonimos.html
[6]: Action(T) Delegate (System) - http://msdn.microsoft.com/en-us/library/018hxwa8(v=vs.110).aspx
[7]: Func(TResult) Delegate (System) - http://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx
[8]: Action(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) Delegate (System) - http://msdn.microsoft.com/en-us/library/dd402872(v=vs.110).aspx
[9]: Func(TResult) Delegate (System) - http://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx
[10]: Variables y Parámetros en C# - Parte 2 | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2013/09/variables-y-parametros-en-c-parte-2.html


J

Delegados en C# - Parte 4: Compatibilidad de Delegados

Tabla de Contenido

0. Introducción
1. Compatibibilidad de Tipos
2. Compatibilidad de Parámetros
3. Compatibilidad de Tipo de Retorno
4. Conclusiones
5. Glosario
6. Literatura & Enlaces

0. Introducción

En penúltima entrega de la serie de artículos de delegados en C#, profundizaremos en la compatibilidad de delegados. Comprenderemos las formas permitidas de asignación de métodos (estáticos o de instancia) a definiciones de delegados que poseen la misma firma. De igual importancia, el tema de la compatibilidad de parámetros con tipos de delegados; el tipo de retorno también será estudiado para conocer sus casos de compatibilidad.

1. Compatibilidad de Tipos

Cualquier tipo de delegado es incompatible con cualquier otro, inclusive si ellos poseen las mismas firmas (signaturas). Por ejemplo:

public delegate void Delegado1();
public delegate void Delegado2();

Definamos el ejemplo completo para demostrar que el intento de asignación de una instancia de delgado sobre otro tipo de delegado con la misma firma generará error de compilación:

Archivo de código fuente CompatibilidadDelegados.cs [enlace alternativo]:
En la línea 15 el intento de asignación Delegado2 d2 = d1; generá el error CS0029 [2]:

error CS0029: Cannot implicitly convert type `Articulos.Cap04.Delegado1' to `Articulos.Cap04.Delegado2'

> Prueba de compilación.

Sin embargo, como nos advierte en [1], esto es posible:

D2 d2 = new D2 (d1);

Continuando, dos instancias de un delegado son iguales siempre y cuando hagan referencia a lo mismo métodos (aquellos alojados en la pila de referencia de métodos); por ejemplo:

public delegate void Delegado();

Delegado d1 = Metodo;
Delegado d2 = Metodo;

Console.WriteLine (d1 == d2); // True

Para los delegados multicast, dos instancias de delegados son iguales si y solo si éstas hacen referencia a los mismos métodos en el mismo orden.

Archivo de código fuente IgualdadMulticast.cs [enlace alternativo]:

Observemos que en los métodos Metodo1Metodo2, y Metodo2 son todos compatibles con el delegado Delegado (declarado en la línea 5); luego en la línea 11 se crea un instancia de Delegado -d1- y asignamos los tres métodos anteriores a modo de multicast. Lo mismo ocurre para la instancia -d2-. Comparamos estas dos instancias -d1 == d2- y el resultado de la evaluación es True, debido a que los métodos fueron agregados en el mismo orden a ambas instancias.

Por otro lado, la evaluación de -d1 == d3- genera False, esto, debido a que el orden de agregación de los métodos en las instancias comparadas no es igual:

Delegado d1 = Metodo1;
d1 = Metodo2;
d1 = Metodo3;

-y-

Delegado d3 = Metodo1;
d3 = Metodo3;
d1 = Metodo2;

> Prueba de ejecución.

Resultado:

¿`d1` y `d2` son iguales?: True
¿`d1` y `d3` son iguales?: False

2. Compatibilidad de Parámetros

La compatibilidad de parámetros consiste en suplir argumentos de tipos compatibles (jerarquía de herencia) con los definidos en la firma. (This is ordinary polymorphic behavior [1]). Ya hemos mencionado este concepto -contra-varianza- en la sección 5. Covarianza y Contra-varianza en Delegados en C# - Parte 1: Introducción.

No está de más volver a ejemplificar para afianzar este concepto de compatibilidad de parámetros de un método:

Archivo de código fuente Contravarianza.cs [enlace alternativo]:

En la línea 5 creamos el delegado AccionSobreString con un parámetro string; luego en la clase Aplicacion creamos el método:

static void MetodoParamObject(object paramObject)
{
Console.WriteLine(paramObject);
}

Este método es compatible con la firma del delegado AccionSobreString a pesar de que el parámetro es object. A este efecto de hacer compatibilidad del tipo de parámetro del delegado al parámetro de un método se le conoce como contravarianza. También podríamos llamar a este efecto compatibilidad hacia atrás.

> Prueba de ejecución.

3. Compatibilidad de Tipo de Retorno

Aquí aparece el concepto de covarianza (más detalles en la sección 5. Covarianza y Contra-varianza en Delegados en C# - Parte 1: Introducción): en este contexto consiste en la situación en la que invocamos un método que retorna un tipo de dato más específico que el declarado como tipo de retorno en la firma del delegado.

Comprendamos lo anterior con un ejemplo:

Archivo de código fuente Varianza.cs [enlace alternativo]:
En la línea 5 se crea el delegado:

delegate object AccionObject();

Este delegado, como es evidente, retorna un tipo object.

Más adelante, en las líneas 19-22 declaramos:

public static string ObtenerString()
{
return "Blog xCSw";
}

El cual retorna un objeto de tipo string.

En el código cliente (líneas 9-17) creamos una instancia del delegado AccionObject, y hacemos referencia al método ObtenerString:

AccionObject del = new AccionObject(ObtenerString);

esta operación es permitida gracias a la compatibilidad hacia adelante, o covarianza: podemos convertir el tipo de retorno en uno compatible en una jerarquía de herencia.

> Prueba de ejecución.

Resultado:


Contenido de `resultado`: Blog xCSw

4. Conclusiones

Aprendimos varios conceptos en esta entrega: la compatibilidad de tipos (un delegado no es compatible con otro a pesar de que posea la misma firma que su homólogo); la contravarianza permite la compatibilidad hacia atrás, es decir, podemos convertir el tipo de los parámetros a otro más general; el concepto opuesto al anterior, es la covarianza, en donde podemos lograr la compatibilidad hacia adelante, es decir, podemos convertir el tipo de retorno de un delegado a uno más específico.

5. Glosario

  • Compatibilidad
  • Compatibilidad hacia atrás
  • Compatibilidad hacia adeleante
  • Contravarianza
  • Covarianza
  • Delegado
  • Delegate

6. Literatura & Enlaces

[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: Compiler Error CS0029 - http://msdn.microsoft.com/en-us/library/xzhh5fx5.aspx


J