example event ejemplo delegate crear c# events delegates binary-compatibility

delegate - eventhandler c# ejemplo



¿Qué perdería al abandonar el patrón estándar de EventHandler en.NET? (2)

Nada, no pierdes nada. He estado usando Action<> desde que salió .NET 3.5 y es mucho más natural y más fácil de programar en contra.

Ya ni siquiera trato con el tipo EventHandler para los controladores de eventos generados, simplemente escriba la firma del método que desee y conéctela con un lambda:

btnCompleteOrder.OnClick += (o,e) => _presenter.CompleteOrder();

Hay un patrón estándar para eventos en .NET: usan un tipo de delegate que toma un objeto simple llamado remitente y luego la "carga útil" real en un segundo parámetro, que debe derivarse de EventArgs .

La razón para el segundo parámetro derivado de EventArgs parece bastante clara (consulte la Referencia anotada de la biblioteca estándar de .NET Framework ). Está pensado para garantizar la compatibilidad binaria entre fuentes y fuentes de eventos a medida que el software evoluciona. Para cada evento, incluso si solo tiene un argumento, derivamos una clase de argumentos de evento personalizados que tiene una propiedad única que contiene ese argumento, de modo que conservamos la capacidad de agregar más propiedades a la carga útil en versiones futuras sin romper el código de cliente existente . Muy importante en un ecosistema de componentes desarrollados independientemente.

Pero me parece que lo mismo vale para cero argumentos. Esto significa que si tengo un evento que no tiene argumentos en mi primera versión, y escribo:

public event EventHandler Click;

... entonces lo estoy haciendo mal. Si cambio el tipo de delegado en el futuro a una nueva clase como su carga útil:

public class ClickEventArgs : EventArgs { ...

... Romperé la compatibilidad binaria con mis clientes. El cliente termina vinculado a una sobrecarga específica de un método interno add_Click que toma EventHandler , y si cambio el tipo de delegado, no pueden encontrar esa sobrecarga, por lo que hay una MissingMethodException .

Bien, ¿y si uso la versión genérica?

public EventHandler<EventArgs> Click;

No, sigue mal, porque un EventHandler<ClickEventArgs> no es un EventHandler<EventArgs> .

Entonces, para obtener el beneficio de EventArgs , debe derivarse de él, en lugar de utilizarlo directamente tal como está. Si no lo hace, puede que no lo esté usando (me parece).

Luego está el primer argumento, sender . Me parece una receta para un acoplamiento impío. Una activación de eventos es esencialmente una llamada de función. ¿Debería la función, en general, tener la capacidad de excavar en la pila y averiguar quién era la persona que llamó y ajustar su comportamiento en consecuencia? ¿Deberíamos exigir que las interfaces se vean así?

public interface IFoo { void Bar(object caller, int actualArg1, ...); }

Después de todo, el implementador de Bar podría querer saber quién era la caller que caller , ¡para que puedan consultar información adicional! Espero que estés vomitando por ahora. ¿Por qué debería ser diferente para los eventos?

Así que incluso si estoy preparado para asumir el dolor de crear una EventArgs -derived independiente para cada evento que declare, solo para que valga la pena usar EventArgs , definitivamente preferiría descartar el argumento del remitente del objeto.

La función de autocompletar de Visual Studio no parece importar qué delegado uses para un evento: puedes escribir += [pulsar Espacio, Retorno] y escribe un método de controlador para ti que coincida con el delegado que sea.

Entonces, ¿qué valor perdería al desviarme del patrón estándar?

Como una pregunta adicional, ¿C # / CLR 4.0 hará algo para cambiar esto, tal vez a través de la contravarianza en los delegados? Intenté investigar esto pero encontré otro problema . Originalmente incluí este aspecto de la pregunta en esa otra pregunta, pero causó confusión allí. Y parece un poco dividir esto en un total de tres preguntas ...

Actualizar:

Resulta que tenía razón al preguntarme sobre los efectos de la contravarianza en todo este asunto.

Como se señaló en otra parte , las nuevas reglas del compilador dejan un agujero en el sistema de tipos que explota en tiempo de ejecución. El agujero se ha tapado efectivamente definiendo EventHandler<T> diferente a Action<T> .

Por lo tanto, para los eventos, para evitar ese tipo de agujero, no debe usar la Action<T> . Eso no significa que tenga que usar EventHandler<TEventArgs> ; solo significa que si usa un tipo de delegado genérico, no elija uno que esté habilitado para la contravarianza.


Tampoco me gusta el patrón del controlador de eventos. En mi opinión, el objeto Sender no es realmente tan útil. En los casos en que un evento dice que algo le sucedió a algún objeto (por ejemplo, una notificación de cambio) sería más útil tener la información en EventArgs. El único uso que podría ver un poco del remitente sería cancelar la suscripción a un evento, pero no siempre queda claro a qué evento se debe cancelar la suscripción.

Por cierto, si tuviera mis druthers, un evento no sería un método AddHandler y un método RemoveHandler; solo sería un método AddHandler que devolvería un MethodInvoker que podría usarse para cancelar la suscripción. En lugar de un argumento de remitente, el primer argumento sería una copia del método de Invocador requerido para cancelar la suscripción (en caso de que un objeto se encuentre recibiendo eventos en los que se perdió el invocador de cancelación de suscripción). El estándar MulticastDelegate no sería adecuado para el envío de eventos (ya que cada suscriptor debería recibir un delegado de cancelación de suscripción diferente), pero los eventos de cancelación de suscripción no requieren una búsqueda lineal a través de una lista de invocación.