Compartir a través de


El patrón de eventos de .NET Core actualizado

Anterior

En el artículo anterior se describen los patrones de eventos más comunes. .NET Core tiene un patrón más relajado. En esta versión, la definición de EventHandler<TEventArgs> ya no tiene la restricción que TEventArgs debe ser una clase derivada de System.EventArgs.

Esto aumenta la flexibilidad para usted y es compatible con versiones anteriores. Comencemos con la flexibilidad. La implementación de System.EventArgs usa un método definido en System.Object un método: MemberwiseClone(), que crea una copia superficial del objeto . Ese método debe usar la reflexión para implementar su funcionalidad para cualquier clase derivada de EventArgs. Esa funcionalidad es más fácil de crear en una clase derivada específica. Esto significa eficazmente que derivar de System.EventArgs es una restricción que limita los diseños, pero no proporciona ninguna ventaja adicional. De hecho, puede cambiar las definiciones de FileFoundArgs y SearchDirectoryArgs para que no deriven de EventArgs. El programa funciona exactamente igual.

También puede cambiar SearchDirectoryArgs a un struct si realiza un cambio más:

internal struct SearchDirectoryArgs
{
    internal string CurrentSearchDirectory { get; }
    internal int TotalDirs { get; }
    internal int CompletedDirs { get; }

    internal SearchDirectoryArgs(string dir, int totalDirs, int completedDirs) : this()
    {
        CurrentSearchDirectory = dir;
        TotalDirs = totalDirs;
        CompletedDirs = completedDirs;
    }
}

El cambio adicional consiste en llamar al constructor sin parámetros antes de escribir el constructor que inicializa todos los campos. Sin esa adición, las reglas de C# informarían de que se accede a las propiedades antes de ser asignadas.

No debe cambiar el FileFoundArgs de una clase (tipo de referencia) a una estructura (tipo de valor). El protocolo para gestionar la cancelación requiere que se pasen argumentos de evento por referencia. Si realizó el mismo cambio, la clase de búsqueda de archivos nunca pudo observar los cambios realizados por cualquiera de los suscriptores de eventos. Se usaría una nueva copia de la estructura para cada suscriptor y esa copia sería una copia diferente de la vista por el objeto de búsqueda de archivos.

A continuación, consideremos cómo este cambio puede ser compatible con versiones anteriores. La eliminación de la restricción no afecta a ningún código existente. Los tipos de argumento de evento existentes siguen derivando de System.EventArgs. La compatibilidad con versiones anteriores es una de las principales razones por las que continúan derivando de System.EventArgs. Los suscriptores de eventos existentes son suscriptores a un evento que siguió el patrón clásico.

Después de una lógica similar, cualquier tipo de argumento de evento creado ahora no tendría suscriptores en ningún código base existente. Los nuevos tipos de eventos que no derivan de System.EventArgs no interrumpen esos códigos base.

Eventos con suscriptores Async

Todavía debe aprender un último patrón: cómo escribir correctamente suscriptores de eventos que llaman a código asincrónico. Este reto se describe en el artículo sobre async y await. Los métodos asincrónicos pueden tener un tipo de valor devuelto void, pero esto no es recomendable. Cuando el código del suscriptor de eventos llama a un método asincrónico, no tiene ninguna opción, sino crear un método async void. La firma del controlador de eventos lo requiere.

Debe conciliar estas directrices contrapuestas. De alguna manera, debe crear un método async void seguro. Los conceptos básicos del patrón que debe implementar se muestran en el código siguiente:

worker.StartWorking += async (sender, eventArgs) =>
{
    try
    {
        await DoWorkAsync();
    }
    catch (Exception e)
    {
        //Some form of logging.
        Console.WriteLine($"Async task failure: {e.ToString()}");
        // Consider gracefully, and quickly exiting.
    }
};

En primer lugar, observe que el controlador está marcado como un controlador asincrónico. Dado que se va a asignar a un tipo de delegado de controlador de eventos, tiene un tipo de valor devuelto void. Esto significa que debe seguir el patrón que se muestra en el controlador y no permitir que se produzcan excepciones fuera del contexto del controlador asincrónico. Dado que no devuelve una tarea, no hay ninguna tarea que pueda notificar el error escribiendo el estado defectuoso. Dado que el método es asincrónico, el método no puede lanzar la excepción. (El método de llamada continúa la ejecución porque es async). El comportamiento real en tiempo de ejecución se define de forma diferente para distintos entornos. Se puede terminar el subproceso o el proceso que posee el subproceso, o dejar el proceso en un estado indeterminado. Todos estos resultados potenciales no son muy deseables.

Debe encapsular la expresión await para una tarea asincrónica en su propio bloque try. Si esto genera una tarea con error, puede registrar el error. Si se trata de un error desde el que la aplicación no se puede recuperar, puede salir del programa de forma rápida y correcta.

En este artículo se explican las actualizaciones principales del patrón de eventos de .NET. Es posible que vea muchos ejemplos de las versiones anteriores en las bibliotecas con las que trabaja. Sin embargo, también debe comprender cuáles son los patrones más recientes. Puede ver el código terminado del ejemplo en Program.cs.

El siguiente artículo de esta serie le ayuda a distinguir entre el uso de delegates y events en los diseños. Son conceptos similares y ese artículo le ayuda a tomar la mejor decisión para sus programas.

Siguiente