jueves, 5 de junio de 2014

Eventos en C# - Parte 2: Patrón de Eventos Estándar en Microsoft .NET Framework

Índice

0. Introducción
1. Patrón de Eventos Estándar
1.1 EventArgs
1.2 Delegado no-genérico integrado: EventHandler
1.3 Delegado genérico integrado: EventHandler<TEventArgs>
2. Pasos de Implementación del Patrón de Eventos Estándar
2.1 Caso de implementación completa
3. Conclusiones
4. Glosario
5. Literatura & Enlaces

0. Introducción

Esta es la segunda parte de la serie de artículos dedicados a tratar otra de las construcciones trascendentes en Microsoft Framework .NET: los eventos. En esta ocasión vamos a conocer el patrón de eventos estándar que implementa el Framework .NET para el diseño de clases publicadoras, clases con datos de eventos (EventArgs), y las clases subscriptoras. Presentaré dos ejemplos: la primera consistirá en el uso del patrón de eventos sin delegados genéricos, y la segunda la que promueve el uso de delegados genéricos.

1. Patrón de Eventos Estándar

Microsoft .NET Framework permite a los programadores implementar el estándar para el patrón de eventos a través del seguimiento de las directrices para el diseño de eventos en aplicaciones. Este patrón es análogo al patrón de Publicar-subscribir [5]. Este patrón como ya hemos visto, consiste en la publicación de mensajes por parte de una clase publicadora (o broadcaster/publisher) y consumidos por un conjunto de clientes (subscribers).

Continuando, este patrón cuenta con los artefactos necesarios en la biblioteca de clases de .NET Framework para hacer más fácil su implementación. Por ejemplo, contamos con la clase base (superclase) EventArgs [6] (en la siguiente sub-sección exploramos este artefacto con más detalle). Podemos crear una jerarquía de herencia a partir de esta clase para crear tipos que soporten (a modo de contenedores) los datos generados durante la generación de un evento.

1.1 EventArgs


La clase EventArgs [6] puede ser concebida como un receptáculo para los datos generados por un evento (i.e, clic del mouse (posición x-y del cursor, botón presionado, etc.), temperatura (hora del cambio de temperatura, los datos en grados Fahrenheit y Celsius, entre otros), &c). Sin embargo, se deben escribir clases cliente que implementen una descripción específica: para el caso de la temperatura crearíamos la siguiente jerarquía de herencia:
Jerarquía de herencia de EventArgs
Figura 1. Jerarquía de herencia de EventArgs.

Empecemos por detallar la estructura de EventArgs. Esta clase sólo posee un constructor, y un campo static y readonly nombrado Empty; se usa parar señalar que un evento ocurrido no contiene datos asociados así mismo. Este valor se debe pasar a los métodos subscritos. Veamos este ejemplo para comprenderlo mejor:

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

Observemos como en la línea 25 se invoca al método de suceso OnLimiteAlcanzado con el argumento EventArgs.Empty cuando ya hemos superado el límite impuesto (número pseudoaleatorio) en la creación de una instancia de Contador (línea 53).


El hecho de pasemos EventArgs.Empty (línea 25) al método de suceso OnLimiteAlcanzado (líneas 29-42) es porque no nos interesa asociar ninguna información (ni siquiera el valor con el que se superó el límite) cuando suceda el evento de superación de límite.

Resultado:
Resultado de prueba del campo Empty de EventArgs
Figura 2. Resultado de prueba del campo Empty de EventArgs.

Continuando con la descripción de la jerarquía de herencia en la Figura 1, la clase CambioTemperaturaEventArgs hereda directamente de EventArgs. En su declaración interna, la clase CambioTemperaturaEventArgs es capaz de mantener la información asociada con un evento de cambio de temperatura, específicamente: la temperatura anterior (temperaturaAnterior), y la temperatura actual (temperaturaActual). Normalmente los campos anteriores se marcan como readonly o como una propiedad de sólo lectura (get), debido a que corresponde con datos que sólo nos interesa leer o consultar del evento ocurrido.

En las dos sub-secciones siguientes introduciré los delegados estándar (parte del patrón de eventos) incorporados en el Framework .NET.

1.2 Delegado no-genérico integrado: EventHandler

.NET Framework cuenta con la definición de un delegado no-genérico para la implementación del patrón de eventos estándar. Se trata de EventHandler [3] que posee la siguiente firma:

public delegate void EventHandler(Object sender, EventArgs e)

Descripción puntual:
  • Object: broadcaster o emisor del evento.
  • EventArgs: contenedor de datos relacionados con el evento.
  • void: No retorna ningún tipo de valor.
(De acuerdo con las directivas halladas en [1, 2], cualquier delegado creado por el programador debe terminar con el postfijo EventHandler para señalar que se trata de un delegado manejador directo de eventos.)


[Nota: En la sección dos veremos un ejemplo usando esta versión de delegado no-genérico.]

1.3 Delegado genérico integrado: EventHandler<TEventArgs>

En el namespace System podemos hallar el delegado genérico integrado EventHandler<TEventArgs> [4] que posee la siguiente firma:

public delegate void EventHandler(Object sender, TEventArgs e) 
where TEventArgs : EventArgs

Descripción puntual:
  • Object: broadcaster o emisor del evento.
  • TEventArgs: parámetro de tipo genérico (hereda de EventArgswhere TEventArgs : EventArgs).
  • void: No retorna ningún tipo de valor.

2. Pasos de Implementación del Patrón de Eventos Estándar

A continuación la descripción (adaptada de [1]) para la implementación del patrón de eventos estándar de Microsoft .NET Framework:

Paso 1: Definición de clase contenedora de datos de evento:

public class PrecioCambioEventArgs : EventArgs
{
public readonly decimal PrecioAnterior;
public readonly decimal PrecioActual;

public PrecioCambioEventArgs(decimal ultimoPrecio, decimal nuevoPrecio)
{
PrecioAnterior = ultimoPrecio;
PrecioActual = nuevoPrecio;
}
}


Vale sumar que los campos readonly PrecioAnterior, y PrecioActual, pudieron haberse especificado como propiedades. Por ejemplo:


public decimal PrecioAnterior
{
get
{
return precioAnterior;
}
}


En [1] sugieren que la nomenclatura para las clases que deriven de EventArgs, deben seguir estas directivas o reglas:
  1. Agregar el postfijo EventArgs al nombre de la clase.
  2. Su nombre (prefijo) debe ser alusivo al tipo de dato que contienen, en lugar del evento para el evento que será usado.
Lo anterior para alcanzar la propiedad de reusabilidad sobre los tipos que definamos.

Paso 2: Definición de delegado

Podemos utilizar cualquier de las versiones definidas en las secciones 1.2 y 1.3. Depende del uso que vayamos a dar a nuestros eventos, o de los requerimientos explícitos de nuestra aplicación.

Como ejemplo:

Versión personalizada:

public delegate void PrecioCambioEventHandler (Object sender, PrecioCambioEventArgs e);

Paso 3: Definición del evento

Podemos utilizar cualquier de estos pasos para la definición del evento:

1. En caso de que no hayamos definido una clase contenedora de datos de evento (e.g., PrecioCambioEventArgs), recurrimos al uso del delegado no-genérico:

public event EventHandler PrecioCambio;

-o-

2. Contamos con una clase contenedora de datos de evento (e.g., PrecioCambioEventArgs), y tenemos definido un delegado:

public event PrecioCambioEventHandler PrecioCambio;

-o-

3. Contamos con una clase contenedora de datos de evento (e.g., PrecioCambioEventArgs), y decidimos usar la versión genérica del delegado incorporado (sección 1.3):

public event EventHandler<PrecioCambioEventArgs> PrecioCambio;

Paso 4: Método de respuesta a la generación del evento

Este último paso consiste en declarar un método que se dispare cuando el evento de la clase publicadora se haya generado. Estas son las características que debe poseer:
  • protected: modificador de acceso (Modificadores de Acceso) que permite que las clases que hereden de la clase publicadora puedan sobrescribir.
  • virtual: Permite la sobreescritura del método en una subclase. Esto a través de override (Miembros Función Virtuales).
  • Prefijo On: Este prefijo debe ser colocado al inicio del identificador del método para indicar que se ejecuta cuando el evento se genera.
Verbigracia:

protected virtual OnPrecioCambio(PrecioCambioEventArgs e) {}

2.1 Caso de implementación completa

Sigamos los pasos sugeridos para crear un ejemplo completo.
Modelo del Programa Almacen con Eventos
Figura 3. Modelo del Programa Almacen con Eventos0

Archivo de código fuente Almacen.cs [enlace alternativo]:
code02
En las líneas 6-16 creamos la clase PrecioCambioEventArgs (paso 1 sección 2) que hereda de EventArgs, y que es utilizada para la contención de los datos del evento.

Creamos el evento (paso 2 y 3PrecioCambio (línea 26) a partir de la definición genérica del delegado EventHandler<TEventArgs>:

public event EventHandler<PrecioCambioEventArgs> PrecioCambio;

Finalmente, en las líneas 28-34 creamos el método de respuesta (paso 4) a la generación del evento: OnPrecioCambio.

> Prueba de ejecución.

Resultado:
Resultado de la implementación del patrón de eventos
Figura 4. Resultado de la implementación del patrón de eventos.

3. Conclusiones

Hasta este punto tenemos una comprensión clara de lo que consiste el patrón de eventos estándar. Dimos lugar al establecimientos de los pasos y directivas que debemos a seguir para implementar este patrón, junto con un caso de implementación concreta para aterrizar conceptos. La siguiente entregará estará dedicada a mutadores (mutatores, setters) y de acceso (getters).

4. Glosario

  • Delegado genérico
  • Delegado no-genérico
  • Directiva
  • EventArgs
  • Eventos
  • Implementación
  • Patrón
  • Reusabilidad

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]: How to: Publish Events that Conform to .NET Framework Guidelines (C# Programming Guide) - http://msdn.microsoft.com/en-us/library/w369ty8x.aspx
[3]: EventHandler Delegate (System) - http://msdn.microsoft.com/en-us/library/system.eventhandler(v=vs.110).aspx
[4]: EventHandler(TEventArgs) Delegate (System) - http://msdn.microsoft.com/en-us/library/db0etb8x(v=vs.110).aspx
[5]: Publish–subscribe pattern - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern
[6]: EventArgs Class (System) - http://msdn.microsoft.com/en-us/library/system.eventargs.aspx
[7]: Modificadores de Acceso en C# | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/04/modificadores-de-acceso-en-c.html
[8]: Miembros Función Virtuales en C# | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2013/12/miembros-funcion-virtuales-en-c.html


J

No hay comentarios:

Publicar un comentario

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