tutorial the example event delegate custom and c# events

the - En un controlador de eventos C#, ¿por qué el parámetro "emisor" debe ser un objeto?



raise event c# (12)

Bueno, es un patrón en lugar de una regla. Significa que un componente puede reenviar en un evento desde otro, manteniendo el remitente original incluso si no es el tipo normal que plantea el evento.

Estoy de acuerdo en que es un poco extraño, pero probablemente valga la pena adherirse a la convención solo por la familiaridad. (Familiaridad con otros desarrolladores, eso es). Nunca me he interesado particularmente en EventArgs (dado que por sí solo no transmite información) pero ese es otro tema. (Al menos ahora tenemos EventHandler<TEventArgs> , aunque ayudaría si también hubiera un EventArgs<TContent> para la situación común en la que solo se necesita propagar un solo valor).

EDITAR: El delegado tiene un propósito más general, por supuesto: un solo tipo de delegado puede reutilizarse en múltiples eventos. No estoy seguro de comprarlo como una razón particularmente buena, particularmente a la luz de los genéricos, pero supongo que es algo ...

Según las directrices de nomenclatura de eventos de Microsoft , el parámetro del sender en un controlador de eventos C # " siempre es del tipo objeto, incluso si es posible utilizar un tipo más específico".

Esto lleva a muchos códigos de manejo de eventos como:

RepeaterItem item = sender as RepeaterItem; if (item != null) { /* Do some stuff */ }

¿Por qué la convención desaconseja declarar un controlador de eventos con un tipo más específico?

MyType { public event MyEventHander MyEvent; } ... delegate void MyEventHander(MyType sender, MyEventArgs e);

¿Me estoy perdiendo un gotcha?

Para la posteridad: estoy de acuerdo con el sentimiento general en las respuestas que la convención es usar objeto (y pasar datos a través de EventArgs ) incluso cuando es posible usar un tipo más específico, y en la programación del mundo real es importante sigue la convención.


Bueno, esa es una buena pregunta. Creo que porque cualquier otro tipo podría usar su delegado para declarar un evento, por lo que no puede estar seguro de que el tipo de remitente sea realmente "MyType".


Creo que hay una buena razón para esta convención.

Tomemos (y ampliémoslo) el ejemplo de @ erikkallen:

void SomethingChanged(object sender, EventArgs e) { EnableControls(); } ... MyRadioButton.Click += SomethingChanged; MyCheckbox.Click += SomethingChanged; MyDropDown.SelectionChanged += SomethingChanged; ...

Esto es posible (y lo ha sido desde .Net 1, antes de los genéricos) porque la covarianza es compatible.

Su pregunta tiene total sentido si va de arriba hacia abajo, es decir, necesita el evento en su código, por lo que lo agrega a su control.

Sin embargo, la convención es hacer que sea más fácil cuando se escriben los componentes en primer lugar. Sabes que para cualquier evento, el patrón básico (remitente del objeto, EventArgs e) funcionará.

Cuando agrega el evento, no sabe cómo se usará, y no desea restringir arbitrariamente a los desarrolladores que usan su componente.

Su ejemplo de un evento genérico fuertemente tipado tiene sentido en su código, pero no encajará con otros componentes escritos por otros desarrolladores. Por ejemplo, si quieren usar su componente con los anteriores:

//this won''t work GallowayClass.Changed += SomethingChanged;

En este ejemplo, la restricción de tipo adicional solo está creando problemas para el desarrollador remoto. Ahora tienen que crear un nuevo delegado solo para su componente. Si están usando una carga de sus componentes, es posible que necesiten un delegado para cada uno.

Considero que vale la pena seguir la convención para cualquier cosa externa o que espere que se use fuera de un equipo cercano.

Me gusta la idea del argumento genérico args: ya uso algo similar.


El patrón de uso de EventHandler (objeto remitente, EventArgs e) está destinado a proporcionar a todos los eventos los medios para identificar el origen del evento (remitente) y proporcionar un contenedor para toda la carga útil específica del evento. La ventaja de este patrón es también que permite generar una cantidad de eventos diferentes usando el mismo tipo de delegado.

En cuanto a los argumentos de este delegado predeterminado ... La ventaja de tener una sola bolsa para todo el estado que desea pasar junto con el evento es bastante obvia, especialmente si hay muchos elementos en ese estado. Usar un objeto en lugar de un tipo fuerte permite pasar el evento, posiblemente a ensamblajes que no tienen una referencia a tu tipo (en cuyo caso puedes argumentar que no podrán usar el remitente de todos modos, pero esa es otra historia - aún pueden obtener el evento).

En mi propia experiencia, estoy de acuerdo con Stephen Redd, muy a menudo el remitente no se usa. Los únicos casos que he necesitado para identificar al remitente es en el caso de los manejadores de UI, con muchos controles que comparten el mismo controlador de eventos (para evitar la duplicación de código). Me alejo de su posición, sin embargo, en el sentido de que no veo ningún problema para definir delegados fuertemente tipados y generar eventos con firmas fuertemente tipadas, en el caso en que sé que al controlador nunca le importará quién es el remitente (de hecho, a menudo no debería tener cualquier alcance en ese tipo), y no quiero la inconveniencia de rellenar el estado en una bolsa (subclase EventArg o genérico) y desempaquetarlo. Si solo tengo 1 o 2 elementos en mi estado, estoy bien generando esa firma. Es una cuestión de conveniencia para mí: mecanografía fuerte significa que el compilador me mantiene alerta, y reduce el tipo de ramificación como

Foo foo = sender as Foo; if (foo !=null) { ... }

que hace que el código se vea mejor :)

Dicho esto, es solo mi opinión. Me he desviado a menudo del patrón recomendado para eventos, y no he sufrido ninguno por ello. Es importante tener siempre claro por qué está bien desviarse de él. ¡Buena pregunta! .


Es porque nunca puedes estar seguro de quién disparó el evento. No hay forma de restringir qué tipos tienen permitido disparar un determinado evento.


Las convenciones existen solo para imponer consistencia.

¿PUEDE escribir fuertemente sus controladores de eventos si lo desea, pero pregúntese si hacerlo proporcionaría alguna ventaja técnica?

Debería considerar que los controladores de eventos no siempre necesitan enviar el remitente ... la mayor parte del código de gestión de eventos que he visto en la práctica real no utiliza el parámetro del remitente. Está ahí SI es necesario, pero a menudo no lo es.

A menudo veo casos en los que diferentes eventos en diferentes objetos compartirán un único controlador de eventos común, que funciona porque ese controlador de eventos no se preocupa por quién era el remitente.

Si esos delegados fueran fuertemente tipados, incluso con el uso inteligente de genéricos, sería MUY difícil compartir un controlador de eventos como ese. De hecho, al teclearlo fuertemente, está imponiendo la suposición de que a los manejadores les debería importar qué es el remitente, cuando esa no es la realidad práctica.

Supongo que lo que debería preguntar es ¿por qué escribiría con fuerza el evento que maneja a los delegados? Al hacerlo, ¿agregaría ventajas funcionales significativas? ¿Estás haciendo el uso más "consistente"? ¿O solo estás imponiendo suposiciones y limitaciones solo por el bien de escribir fuerte?


Los genéricos y la historia jugarían un papel importante, especialmente con la cantidad de controles (etc.) que exponen eventos similares. Sin genéricos, terminarías con muchos eventos que exponen Control , que en gran medida es inútil:

  • usted todavía tiene que hacer el reparto para hacer algo útil (excepto tal vez un control de referencia, que puede hacer igual de bien con el object )
  • no puede volver a usar los eventos en los no controles

Si consideramos los genéricos, una vez más todo está bien, pero luego comienzas a tener problemas con la herencia; si clase B : A , entonces ¿los eventos en A deben ser EventHandler<A, ...> , y los eventos en B ser EventHandler<B, ...> ? Una vez más, muy confuso, difícil de manejar y un poco complicado en términos de lenguaje.

Hasta que haya una mejor opción que cubra todos estos, el object funciona; los eventos son casi siempre en instancias de clases, por lo que no hay boxeo, etc., solo un elenco. Y el casting no es muy lento.


No hay una buena razón en absoluto, ahora hay covarianza y contravariencia, creo que está bien usar un emisor fuertemente tipado. Ver discusión en esta question


Supongo que es porque deberías ser capaz de hacer algo como

void SomethingChanged(object sender, EventArgs e) { EnableControls(); } ... MyRadioButton.Click += SomethingChanged; MyCheckbox.Click += SomethingChanged; ...

¿Por qué haces el yeso seguro en tu código? Si sabes que solo usas la función como controlador de eventos para el repetidor, sabes que el argumento es siempre del tipo correcto y puedes usar un lanzamiento arrojadizo, por ejemplo, (repetidor) emisor en lugar de (emisor como repetidor).


Tiendo a usar un tipo de delegado específico para cada evento (o un pequeño grupo de eventos similares). El remitente y los eventargs inútiles simplemente saturan la API y distraen de los bits de información realmente relevantes. Ser capaz de "reenviar" eventos a través de las clases no es algo que todavía tenga que encontrar útil, y si reenvía eventos como ese, a un controlador de eventos que representa un tipo diferente de evento, entonces se ve obligado a envolver el evento. usted mismo y proporcione los parámetros adecuados es poco esfuerzo. Además, el reenviador tiende a tener una mejor idea de cómo "convertir" los parámetros del evento que el receptor final.

En resumen, a menos que exista alguna razón urgente de interoperabilidad, elimine los parámetros inútiles y confusos.


Tu dices:

Esto lleva a muchos códigos de manejo de eventos como:

RepeaterItem item = sender as RepeaterItem if (RepeaterItem != null) { /* Do some stuff */ }

¿Realmente es mucho código?

Aconsejo nunca usar el parámetro del sender a un controlador de eventos. Como habrás notado, no está tipado estáticamente. No es necesariamente el remitente directo del evento, porque a veces se reenvía un evento. Por lo tanto, es posible que el mismo controlador de eventos ni siquiera obtenga el mismo tipo de objeto de sender cada vez que se activa. Es una forma innecesaria de acoplamiento implícito.

Cuando te alistas con un evento, en ese momento debes saber en qué objeto se encuentra el evento, y eso es lo que más te interesa:

someControl.Exploded += (s, e) => someControl.RepairWindows();

Y cualquier otra cosa específica para el evento debe estar en el segundo parámetro derivado de EventArgs.

Básicamente, el parámetro del sender es un poco de ruido histórico, es mejor evitarlo.

Hice una pregunta similar aquí.


Utilizo el siguiente delegado cuando preferiría un remitente fuertemente tipado.

/// <summary> /// Delegate used to handle events with a strongly-typed sender. /// </summary> /// <typeparam name="TSender">The type of the sender.</typeparam> /// <typeparam name="TArgs">The type of the event arguments.</typeparam> /// <param name="sender">The control where the event originated.</param> /// <param name="e">Any event arguments.</param> public delegate void EventHandler<TSender, TArgs>(TSender sender, TArgs e) where TArgs : EventArgs;

Esto se puede usar de la siguiente manera:

public event EventHandler<TypeOfSender, TypeOfEventArguments> CustomEvent;