2009-09-17 20 views
67

De acuerdo con Microsoft event naming guidelines, el parámetro sender en un controlador de eventos C# "es siempre de tipo objeto, incluso si es posible usar un tipo más específico".En un controlador de eventos C#, ¿por qué el parámetro "remitente" debe ser un objeto?

Esto lleva a una gran cantidad de código como el manejo de eventos:

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

¿Por qué el consejo de convención contra la que se declara un controlador de eventos con un tipo más específico?

MyType 
{ 
    public event MyEventHander MyEvent; 
} 

... 

delegate void MyEventHander(MyType sender, MyEventArgs e); 

Am I missing a gotcha?

Para la posteridad: Estoy de acuerdo con el sentimiento general en las respuestas que la convención es a utilizar el objeto (y para pasar los datos a través de la EventArgs incluso cuando es posible utilizar un tipo más específico, y en bienes -WORLD programándolo es importante seguir la convención.

+0

En realidad, * todos los tipos * en C# son objetos ... –

+2

Aunque obviamente tiene razón, mi pregunta es por qué la convención es * no * usar un tipo más específico si es posible. –

+0

Sí, un valor de retorno en el controlador de eventos se ve mal. ¿Qué pasa si hay más de un controlador? – erikkallen

Respuesta

38

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

Estoy de acuerdo, es un poco extraño, pero probablemente valga la pena ceñirse a la convención solo por la familiaridad. (Es decir, estoy familiarizado con otros desarrolladores). Nunca me ha interesado particularmente el EventArgs (dado que por sí solo no transmite información) pero ese es otro tema. (Al menos ahora tenemos EventHandler<TEventArgs>, aunque sería útil si también existiera un EventArgs<TContent> para la situación común donde solo se necesita propagar un valor único)

EDITAR: Hace que el delegado sea más general , por supuesto, un solo tipo de delegado se puede reutilizar en varios eventos. No estoy seguro de comprar eso como una razón particularmente buena, especialmente a la luz de los genéricos, pero supongo que es algo ...

+12

Aunque creo que casi con certeza es cierto, todavía no estoy seguro de ver la ventaja. El manejador de eventos aún tiene que saber de qué tipo se está pasando (porque tiene que transmitirlo). No puedo entender por qué sería algo malo escribir con fuerza, aparte de su punto de familiaridad con el patrón existente ... –

+0

+1 para EventArg Benjol

+8

@Iain: Sí, no veo mucha ventaja del patrón que se define de esta manera tampoco. Puede ser una mala decisión de hace muchos años, que ahora es demasiado tarde para cambiar sensiblemente. –

0

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".

+0

Todo lo que he leído sugiere esto. Sin embargo ... Imagine que tiene algún evento en el que el remitente puede ser del tipo A o del tipo B. Luego, en el Manejador de Eventos, debe tratar de convertir a A y luego hacer una lógica A específica, luego intente convertir a B y haz una lógica B-específica. Alternativamente, extraiga una interfaz compartida I de A y B y cree eso. En el primer caso, ¿cuál es la ventaja sobre dos manejadores de eventos separados con un remitente y un remitente B? En el último caso, ¿cuál es la ventaja sobre el emisor de tipado fuerte como yo? –

+0

Pero no podrían levantar el evento sin una instancia de su control (o usar null para el remitente, pero eso no es interesante). – erikkallen

+0

Sí, así es, no pueden criarlo. Lo que escribí arriba es solo mi opinión acerca de por qué se recomienda usar el objeto para el remitente, no dije que estoy absolutamente de acuerdo con él. ;) Pero, por otro lado, creo que uno debe seguir el patrón comúnmente utilizado, y por lo tanto siempre uso object. –

5

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, que terminaría con una gran cantidad de eventos exponer Control, que es en gran medida inútil:

  • todavía tiene que echar a hacer nada útil (excepto tal vez una verificación de referencia, que se puede hacer igual de bien con object)
  • no se puede volver a utilizar los eventos en los no controles

Si tenemos en cuenta los genéricos, y de nuevo todo está bien, pero luego comienza a recibir en problemas con la herencia; si clase B : A, ¿deberían los eventos en A 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, 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.

4

supongo que es porque usted debe ser capaz de hacer algo como

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

¿Por qué haces el reparto seguro en su código? Si sabe que solo usa la función como controlador de eventos para el repetidor, sabe que el argumento es siempre del tipo correcto y puede usar un lanzamiento arrojadizo en su lugar, p. (Repetidor) emisor en lugar de (emisor como Repetidor).

+0

Bueno, ese es mi punto. Si sabes que el argumento es siempre del tipo correcto, ¿por qué no puedes pasar ese tipo? Obviamente, hay * situaciones * en las que desea utilizar un tipo menos específico (como el que acaba de describir). El elenco me parece un poco desordenado. Quizás solo estoy siendo demasiado entusiasta. –

+0

(+1) para el ejemplo, lo he expandido en mi propia respuesta. – Keith

+0

@Iain: agregué una breve explicación sobre mi punto de "transmisión segura". – erikkallen

1

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 donde 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 fueron 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?

1

Usted dice:

Esto lleva a un montón de manejo de eventos código como: -

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

¿Es realmente una gran cantidad de código?

Aconsejo nunca utilizar el parámetro 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, el mismo controlador de eventos puede no obtener el mismo tipo de objeto sender cada vez que se dispara. Es una forma innecesaria de acoplamiento implícito.

al dar de alta a un evento, en ese momento usted debe saber qué objeto es el caso, y eso es lo que es más probable que esté interesado en:

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

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

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

I asked a similar question here.

+0

* asiente * más útil, más seguro y menos oloroso para usar MyType item = e.Item, junto con EventHandler . Me gusta eso. –

1

es porque nunca se puede estar seguro de que disparó el evento. No hay forma de restringir qué tipos tienen permitido disparar un determinado evento.

1

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 su tipo (en cuyo caso puede argumentar que no podrán usar el remitente de todos modos, pero eso es otro historia: todavía pueden obtener el evento).

En mi propia experiencia, estoy de acuerdo con Stephen Redd, muy a menudo el remitente no se utiliza. 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, no veo ningún problema en 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 debería no tiene ningún alcance en ese tipo), y no quiero la inconveniencia de rellenar el estado en una bolsa (subclase o genérico EventArg) 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í: la tipificación estricta significa que el compilador me mantiene en mis dedos de los pies, y reduce el tipo de ramificación como

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

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

Este ser dijo, 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 apartarse de él. ¡Buena pregunta! .

2

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 este question

8

Uso el siguiente delegado cuando prefiero 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 utilizar de la siguiente manera:

public event EventHandler<TypeOfSender, TypeOfEventArguments> CustomEvent; 
+0

+1 Estoy de acuerdo con esto 100%. Tengo una discusión detallada de este enfoque aquí: http://stackoverflow.com/questions/1046016/event-signature-in-net-using-a-strong-typed-sender. –

0

que tienden a utilizar un tipo específico delegado 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 apropiados 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.

17

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

Tomemos (y ampliar) @ ejemplo de erikkallen:

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

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

Su pregunta tiene mucho 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 para facilitar la escritura de los componentes en primer lugar. Usted sabe 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 utilizar su componente con los de arriba:

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

En este ejemplo, el tipo-restricción adicional es sólo la creación de dolor 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.

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

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

+1

Muy buen punto. Ojalá pudiera darte +5. Al usar el objeto para el remitente, el ensamblado que llama ni siquiera necesita saber sobre el tipo al que está adjuntando un evento, solo que deriva de algún tipo que conoce (el más relevante es Control). No podrá usar el parámetro del remitente de forma útil (porque no sabe de qué tipo lo va a convertir), pero puede y recogerá cualquier información de estado que necesite de los EventArgs. –

+0

Sí, mientras tanto, si sabes qué remitente deberías, puedes devolverlo. No puedo imaginar un escenario en el que tenga un evento en un tipo de valor, por lo que no está perjudicando el rendimiento del elenco. – Keith

+0

@IainGalloway and Keith, entonces ... querrías que alguien adjunte el controlador de eventos en la pregunta a un cuadro de texto o botón (o cualquier cosa excepto un 'RepeaterItem') y luego ... ¿qué? El código ingresa y prueba el elenco, devuelve nulo, luego no pasa nada ... si el controlador no está diseñado para manejar un tipo particular de objeto como remitente, entonces, ¿por qué DESEA que se pueda adjuntar? Obviamente, habrá eventos que sean lo suficientemente genéricos como para justificar completamente el uso de 'objeto' con tu lógica, pero ... a menudo no tan bien (?) Me puede estar perdiendo algo, así que siéntete libre de iluminar lo que podría ser. –

Cuestiones relacionadas