jueves, 19 de junio de 2014

Métodos Anónimos en C#

Tabla de Contenido

0. Introducción
1. ¿Qué es un Método Anónimo?
1.1 Historia de los métodos anónimos
2. Operaciones
2.1 Captura de variables externas
2.1.1 Actualización de captured variables
2.1.2 Ciclo de vida de una variable capturada
3. Métodos Anónimos y Threads
4. Comentarios Finales
5. Conclusiones
6. Glosario
7. Literatura & Enlaces

0. Introducción

En este artículo haré un cubrimiento más amplio del uso de métodos anónimos en C#. En la serie de artículos de expresiones lambda aprendimos que este tipo de construcción representa la evolución de los métodos anónimos (métodos sin nombre). En el desarrollo de este artículo comprenderemos cuáles son las diferencias conceptuales y prácticas de los métodos anónimos frente a las expresiones lambda. Los ejemplos que se presentarán, también incluyen la diferencia entre un método anónimo y un método con nombre (o método formal).

1. ¿Qué es un Método Anónimo?

Un método anónimo consiste en la declaración de un método sin nombre (de ahí su nombre). Su uso se conjuga con la encapsulación de un método por parte de un delegado (Delegados en C#). Veamos un uso básico en la declaración de un delegado para el evento Click de la clase Button (System.Windows.Forms):

// Creación del manejador del evento Click del botón:
btnMostrarMensaje.Click += delegate(object sender, EventArgs e)
{
MessageBox.Show ("Clic!");
}

Nótese cómo hemos hecho uso explícito de la lista de los parámetros que requiere el tipo del evento Click (i.e., object sender, EventArgs), la construcción delegate por delante, y un bloque de código con la sentencia que se ejecuta cuando se pulsa el botón btnMostrarMensaje. Todo lo anterior sin crear un método con nombre. Esto nos brinda más flexibilidad y un código más expresivo.

1.1 Historia de los métodos anónimos

Antes de ser lanzada la versión 2.0 de C#, se requería el uso explicito de métodos con nombre para ser referenciados o encapsulados por un delegados. La versión del ejemplo anterior tendría que haberse escrito así:

El método a ser encapsulado:

btnMostrarMensaje.Click += delegate(object sender, EventArgs e)
{
MessageBox.Show ("Clic!");
}

Asignación de la instancia del delegado al evento Click:

btnMostrarMensaje.Click += new EventHandler(btnMostrarMensajeClick);

Como resulta evidente, esta solución requiere más líneas de código y más trabajo de elaboración respecto a la solución con métodos anónimos.

Por otro lado, la incorporación de las expresiones lambda (Expresiones Lambda en C#) en la versión 3.0 de C# representa un paso evolutivo en la simplificación y expresividad del lenguaje, debido a que este tipo construcción lógica se encarga de inferir los tipos de los parámetros y el tipo de retorno.

Ejemplo con expresiones lambda:

// Creación del manejador del evento Click del botón:
btnMostrarMensaje.Click += (sender, e) => MessageBox.Show ("Clic!");

En la lista parentizada (sender, e) omitimos la especificación de los tipos de los parámetros correspondientes. El compilador se encarga de inferir de los tipos a partir de la firma del delegado EventHandler [4]:

public delegate void EventHandler(Object sender, EventArgs e)

Resumamos con el siguiente código fuente C# cómo ha ocurrido esta evolución de uso directo de delegados con métodos anónimos, y la incorporación de expresiones lambda antes de la versión 2.0 y después de la versión 3.0 de C#.

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

En la línea 7 declaramos el delegado con la siguiente firma:
  • Tipo de retorno: int
  • Parámetros: int
Enseguida, en las líneas 9-12, se declara el método estático Cuadrado. Ya en el método Main se prueba con diferentes enfoques con las distintas versiones de C#. Así:
  • C# 1.0:
    • Línea 18: Se crea una instancia del delegado DelegadoCuadrado y se encapsula el método Cuadrado.
    • Línea 19: Se invoca indirectamente al método Cuadrado con el argumento 5.
  • C# 2.0:
    • Líneas 25-28: Se define in situ (inline) un método anónimo compatible con el delegado DelegadoCuadrado. El tipo del parámetro es explícito (int). El tipo de retorno se define por la evaluación entera que se produce al multiplicar parámetro numero por sí mismo.
    • Línea 29Se invoca indirectamente al método Cuadrado con el argumento 7.
  • C# 3.0:
    • Línea 33: declaración de expresión lambda x => x * x. Esta es la expresión más simple, al compararla con las utilizadas en las versión 1.0 y 2.0 de C#.
    • Línea 39: Uso del delegado genérico integrado Func<> con el que se puede prescindir de la declaración explícita de un delegado genérico personalizado.
> Prueba de ejecución.

Resultado:
Prueba de ejecución del assembly EvolucionMetodosAnonimos.exe
Figura 1. Prueba de ejecución del assembly EvolucionMetodosAnonimos.exe.

2. Operaciones

2.1 Captura de variables externas

En el cuerpo de un método anónimo podemos incluir referencias a variables locales y los parámetros de un método. En el contexto de un método anónimo, a estas referencias se les conoce como variables externas.

Para poner como caso:

public static void Main()
{
int factor = 3;

// Delegado genérico integrado:
Func<int, int> producto = delegate(int n) { return n * factor;};

Console.WriteLine (producto (5)); // 15
}


A cualquier variable a la que se referencie en el cuerpo de un método anónimo, se le conoce como captured variable (e.g., factor). Los métodos anóinimos que están compuestas por captured variables (variables capturadas) se les llama closure.

A lo anterior hay que agregar que las captures variables se evalúan una vez que el método anónimo entra en acción. Para demostrarlo, leamos este fragmento de código:

class Demo
{
public static void Main()
{
int factor = 2;

Func<int,int> multiplicador = delegate(int n) { return n * factor;};

factor = 10;

// Usa el valor actualizado de la variable `factor`:
Console.WriteLine (multiplicador(3));
}
}

> Prueba de ejecución.

2.1.1 Actualización de captured variables

Las variables capturadas en un método anónimo se actualizan de forma automática cada vez se invoca indirectamente (al método anónimo) a través del delegado. Veamos este efecto en el siguiente fragmento de código:

int variableLocal = 0;

// Captured variable:
Func<int> delegado = delegate() { return variableLocal++;};

Console.WriteLine (delegado()); // 0
Console.WriteLine (delegado()); // 1
Console.WriteLine (variableLocal); // 2

Prueba de ejecución.

2.1.2 Ciclo de vida de una variable capturada

La referencia y el valor a una variable capturada está disponible mientras el ámbito del delegado esté al alcance de ejecución. 

Ejemplo de uso:

public static Func<int> GeneraDelegado()
{
int variableLocal = 0;
return delegate() { return variableLocal++;}; // Retorna un closure
}

public static void Main()
{
Func<int> natural = GeneraDelegado();

Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1
}

Prueba de ejecución.

3. Métodos Anónimos y Threads

Podemos utilizar la declaración de un método anónimo y encapsularlo en una instancia de un delegado ThreadStart [5], de este modo nos ahorramos la escritura de un método con nombre.

Archivo de código fuente MetodoAnonimoDelegadoThreadStart.cs [enlace alternativo]:
En la línea 10 declaramos una instancia de Thread, y al constructor de este tipo le pasamos una instancia de un delegado compatible con ThreadStart. El cuerpo del delegado, líneas 13-18, incluye un ciclo for que por cada iteración realiza una pausa del thread por 0.5 segundos (línea 17). 

> Prueba de ejecución.

Resultado:
Prueba ejecución assembly MetodoAnonimoDelegadoThreadStart.exe
Figura 2. Prueba ejecución assembly MetodoAnonimoDelegadoThreadStart.exe.

4. Comentarios Finales

Es importante anotar las siguientes consideraciones encontradas en [6]:
"It is an error to have a jump statement, such as goto, break, or continue, inside the anonymous method block if the target is outside the block. It is also an error to have a jump statement, such as gotobreak, or continue, outside the anonymous metehod block if the target is inside the block."
"...There is one case in which an anonymous method provides functionality not found in lambda expressions. Anonymous methods enable you to omit the parameters list. This means that an anonymous method can be converted to delegates with a variety of signatures. This is not possible with lambda expressions..."

5. Conclusiones

Hemos definido el concepto de método anónimo de forma más extendida. Aprendimos que los métodos anónimos fueron introducidos a partir de la versión 2.0 de C#, y permitió la omisión de la declaración de métodos con nombre. Esto facilitó muchas las cosas haciendo más simple la escritura de código para delegado. Sin embargo, con la versión 3.0 de C#, y la aparición de las expresiones lambda, la programación de expresiones in situ (inline) es aún más simple y expresiva.

6. Glosario

  • Delegado
  • Expresión lambda
  • Método anónimo
  • Método formal

7. 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]: Delegados en C#: Introducción | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/05/Delegados-en-csharp-parte-1-introduccion.html
[3]: Expresiones Lambda en C# - Parte 1: Introducción a las Expresiones Lambda - http://ortizol.blogspot.com/2014/06/expresiones-lambda-en-csharp-parte-1-introduccion-a-las-expresiones-lambda.html
[4]: EventHandler Delegate (System) - http://msdn.microsoft.com/en-us/library/system.eventhandler(v=vs.110).aspx
[5]: ThreadStart Delegate (System.Threading) - http://msdn.microsoft.com/en-us/library/system.threading.threadstart.aspx
[6]: Anonymous Methods (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/0yw3tz5k.aspx


J

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.