Me doy cuenta de que lo que propongo no sigue las pautas de .NET y, por lo tanto, probablemente sea una mala idea solo por esta razón. Sin embargo, me gustaría considerar esto desde dos perspectivas posibles:
(1) ¿Debería considerar usar esto para mi propio trabajo de desarrollo, que es 100% para propósitos internos?
(2) ¿Es este un concepto que los diseñadores del marco podrían considerar cambiar o actualizar?
Estoy pensando en usar una firma de evento que utilice un 'remitente' de tipo fuerte, en lugar de escribirlo como 'objeto', que es el patrón de diseño de .NET actual. Es decir, en lugar de usar una firma de evento estándar que se ve así:
class Publisher
{
public event EventHandler<PublisherEventArgs> SomeEvent;
}
Estoy considerando usar una firma de evento que utilice un parámetro de 'remitente' de tipo fuerte, de la siguiente manera:
Primero, defina un "StrongTypedEventHandler":
[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
Esto no es tan diferente de un Action <TSender, TEventArgs>, pero al hacer uso del StrongTypedEventHandler
, hacemos cumplir que el TEventArgs se deriva de System.EventArgs
.
A continuación, como ejemplo, podemos hacer uso de StrongTypedEventHandler en una clase de publicación de la siguiente manera:
class Publisher
{
public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;
protected void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent(this, new PublisherEventArgs(...));
}
}
}
La disposición anterior permitiría a los suscriptores utilizar un controlador de eventos de tipo fuerte que no requiriera conversión:
class Subscriber
{
void SomeEventHandler(Publisher sender, PublisherEventArgs e)
{
if (sender.Name == "John Smith")
{
// ...
}
}
}
Me doy cuenta de que esto rompe con el patrón estándar de manejo de eventos de .NET; sin embargo, tenga en cuenta que la contravarianza permitiría a un suscriptor usar una firma de manejo de eventos tradicional si lo desea:
class Subscriber
{
void SomeEventHandler(object sender, PublisherEventArgs e)
{
if (((Publisher)sender).Name == "John Smith")
{
// ...
}
}
}
Es decir, si un controlador de eventos necesita suscribirse a eventos de tipos de objetos dispares (o tal vez desconocidos), el controlador podría escribir el parámetro 'remitente' como 'objeto' para manejar la gama completa de objetos remitentes potenciales.
Aparte de romper las convenciones (que es algo que no tomo a la ligera, créanme), no puedo pensar en ninguna desventaja de esto.
Puede haber algunos problemas de cumplimiento de CLS aquí. Esto se ejecuta en Visual Basic .NET 2008 100% bien (lo he probado), pero creo que las versiones anteriores de Visual Basic .NET hasta 2005 no tienen covarianza y contravarianza delegadas. [Editar: he probado esto desde entonces, y está confirmado: VB.NET 2005 y versiones anteriores no pueden manejar esto, pero VB.NET 2008 está 100% bien. Consulte "Edición nº 2", a continuación.] Puede que haya otros lenguajes .NET que también tengan un problema con esto, no puedo estar seguro.
Pero no me veo desarrollando para ningún lenguaje que no sea C # o Visual Basic .NET, y no me importa restringirlo a C # y VB.NET para .NET Framework 3.0 y superior. (Para ser honesto, no me puedo imaginar volver a 2.0 en este punto).
¿Alguien más puede pensar en un problema con esto? ¿O esto simplemente rompe con las convenciones tanto que hace que a la gente se le revuelva el estómago?
Aquí hay algunos enlaces relacionados que encontré:
(1) Directrices de diseño de eventos [MSDN 3.5]
(3) Patrón de firma de eventos en .net [StackOverflow 2008]
Me interesa la opinión de todos y de todos sobre esto ...
Gracias por adelantado,
Miguel
Edición n. ° 1: esto es en respuesta a la publicación de Tommy Carlier :
Aquí hay un ejemplo de trabajo completo que muestra que tanto los controladores de eventos de tipo fuerte como los controladores de eventos estándar actuales que usan un parámetro de 'remitente de objeto' pueden coexistir con este enfoque. Puede copiar y pegar el código y ejecutarlo:
namespace csScrap.GenericEventHandling
{
class PublisherEventArgs : EventArgs
{
// ...
}
[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
class Publisher
{
public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;
public void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent(this, new PublisherEventArgs());
}
}
}
class StrongTypedSubscriber
{
public void SomeEventHandler(Publisher sender, PublisherEventArgs e)
{
MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.");
}
}
class TraditionalSubscriber
{
public void SomeEventHandler(object sender, PublisherEventArgs e)
{
MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.");
}
}
class Tester
{
public static void Main()
{
Publisher publisher = new Publisher();
StrongTypedSubscriber strongTypedSubscriber = new StrongTypedSubscriber();
TraditionalSubscriber traditionalSubscriber = new TraditionalSubscriber();
publisher.SomeEvent += strongTypedSubscriber.SomeEventHandler;
publisher.SomeEvent += traditionalSubscriber.SomeEventHandler;
publisher.OnSomeEvent();
}
}
}
Edición # 2: Esto es en respuesta a la declaración de Andrew Hare con respecto a la covarianza y contravarianza y cómo se aplica aquí. Los delegados en el lenguaje C # han tenido covarianza y contravarianza durante tanto tiempo que se siente "intrínseco", pero no lo es. Incluso podría ser algo que esté habilitado en CLR, no lo sé, pero Visual Basic .NET no obtuvo la capacidad de covarianza y contravarianza para sus delegados hasta .NET Framework 3.0 (VB.NET 2008). Y como resultado, Visual Basic.NET para .NET 2.0 y versiones posteriores no podrían utilizar este enfoque.
Por ejemplo, el ejemplo anterior se puede traducir a VB.NET de la siguiente manera:
Namespace GenericEventHandling
Class PublisherEventArgs
Inherits EventArgs
' ...
' ...
End Class
<SerializableAttribute()> _
Public Delegate Sub StrongTypedEventHandler(Of TSender, TEventArgs As EventArgs) _
(ByVal sender As TSender, ByVal e As TEventArgs)
Class Publisher
Public Event SomeEvent As StrongTypedEventHandler(Of Publisher, PublisherEventArgs)
Public Sub OnSomeEvent()
RaiseEvent SomeEvent(Me, New PublisherEventArgs)
End Sub
End Class
Class StrongTypedSubscriber
Public Sub SomeEventHandler(ByVal sender As Publisher, ByVal e As PublisherEventArgs)
MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.")
End Sub
End Class
Class TraditionalSubscriber
Public Sub SomeEventHandler(ByVal sender As Object, ByVal e As PublisherEventArgs)
MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.")
End Sub
End Class
Class Tester
Public Shared Sub Main()
Dim publisher As Publisher = New Publisher
Dim strongTypedSubscriber As StrongTypedSubscriber = New StrongTypedSubscriber
Dim traditionalSubscriber As TraditionalSubscriber = New TraditionalSubscriber
AddHandler publisher.SomeEvent, AddressOf strongTypedSubscriber.SomeEventHandler
AddHandler publisher.SomeEvent, AddressOf traditionalSubscriber.SomeEventHandler
publisher.OnSomeEvent()
End Sub
End Class
End Namespace
VB.NET 2008 puede ejecutarlo 100% bien. Pero ahora lo he probado en VB.NET 2005, solo para estar seguro, y no se compila, indicando:
El método 'Public Sub SomeEventHandler (remitente como objeto, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' no tiene la misma firma que el delegado 'Delegate Sub StrongTypedEventHandler (Of TSender, TEventArgs como System.EventArgs) (ergs como System.EventArgs) (ergs) '
Básicamente, los delegados son invariantes en las versiones de VB.NET 2005 y anteriores. De hecho, pensé en esta idea hace un par de años, pero la incapacidad de VB.NET para lidiar con esto me molestó ... Pero ahora me he movido sólidamente a C #, y VB.NET ahora puede manejarlo, así que, bueno, por lo tanto esta publicación.
Editar: Actualización n. ° 3
Ok, he estado usando esto con bastante éxito por un tiempo. Realmente es un buen sistema. Decidí nombrar mi "StrongTypedEventHandler" como "GenericEventHandler", definido de la siguiente manera:
[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
Aparte de este cambio de nombre, lo implementé exactamente como se discutió anteriormente.
Se tropieza con la regla CA1009 de FxCop, que establece:
"Por convención, los eventos .NET tienen dos parámetros que especifican el remitente del evento y los datos del evento. Las firmas del controlador de eventos deben seguir este formulario: void MyEventHandler (remitente del objeto, EventArgs e). El parámetro 'remitente' es siempre de tipo System.Object, incluso si es posible emplear un tipo más específico. El parámetro 'e' es siempre de tipo System.EventArgs. Los eventos que no proporcionan datos de eventos deben usar el tipo de delegado System.EventHandler. Los controladores de eventos devuelven void para que puedan enviar cada evento a varios métodos de destino. Cualquier valor devuelto por un destino se perdería después de la primera llamada ".
Por supuesto, sabemos todo esto y, de todos modos, estamos rompiendo las reglas. (Todos los controladores de eventos pueden usar el 'remitente de objeto' estándar en su firma si lo prefieren en cualquier caso; este es un cambio irrefutable).
Entonces, el uso de a SuppressMessageAttribute
hace el truco:
[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
Espero que este enfoque se convierta en el estándar en algún momento en el futuro. Realmente funciona muy bien.
Gracias por todas sus opiniones chicos, de verdad se lo agradezco ...
Miguel
oh hi this my hom work solve it plz :code dump:
preguntas del tamaño de un tweet , sino una pregunta de la que aprendemos .EventHandler<,>
queGenericEventHandler<,>
. Ya hay genéricoEventHandler<>
en BCL que se llama simplemente EventHandler. Entonces, EventHandler es un nombre más común y los delegados admiten sobrecargas de tiposRespuestas:
Parece que Microsoft se ha dado cuenta de esto, ya que ahora hay un ejemplo similar en MSDN:
Delegados genéricos
fuente
Lo que propones tiene mucho sentido en realidad, y me pregunto si esta es una de esas cosas que es simplemente la forma en que es porque fue diseñado originalmente antes de los genéricos, o si hay una razón real para esto.
fuente
Windows Runtime (WinRT) presenta un
TypedEventHandler<TSender, TResult>
delegado, que hace exactamente lo que hace ustedStrongTypedEventHandler<TSender, TResult>
, pero aparentemente sin la restricción delTResult
parámetro de tipo:La documentación de MSDN está aquí .
fuente
EventArgs
, es solo una convenciónargs
seránull
si no hay datos de eventos, por lo que parece que se están alejando del uso de un objeto esencialmente vacío de forma predeterminada. Supongo que la idea original era que un método con un segundo parámetro de tipoEventArgs
podría manejar cualquier evento porque los tipos siempre serían compatibles. Probablemente se estén dando cuenta ahora de que poder manejar múltiples eventos diferentes con un método no es tan importante.No estoy de acuerdo con las siguientes declaraciones:
En primer lugar, nada de lo que haya hecho aquí tiene nada que ver con la covarianza o contravarianza.( Editar: la declaración anterior es incorrecta, para obtener más información, consulte Covarianza y contravarianza en delegados ) Esta solución funcionará bien en todas las versiones de CLR 2.0 y posteriores (obviamente, esto no funcionará en una aplicación CLR 1.0 ya que usa genéricos).En segundo lugar, no estoy de acuerdo con que su idea raye en la "blasfemia", ya que es una idea maravillosa.
fuente
Eché un vistazo a cómo se manejó esto con el nuevo WinRT y me basé en otras opiniones aquí, y finalmente me decidí a hacerlo así:
Esta parece ser la mejor manera de avanzar considerando el uso del nombre TypedEventHandler en WinRT.
fuente
Creo que es una gran idea y es posible que MS simplemente no tenga el tiempo o el interés para invertir en mejorar esto, como por ejemplo cuando pasaron de ArrayList a listas genéricas.
fuente
Por lo que tengo entendido, siempre se supone que el campo "Remitente" se refiere al objeto que contiene la suscripción al evento. Si tuviera mis druthers, también habría un campo con información suficiente para cancelar la suscripción de un evento si fuera necesario (*) (considere, por ejemplo, un registrador de cambios que se suscribe a eventos de 'colección cambiada'; contiene dos partes , uno de los cuales hace el trabajo real y contiene los datos reales, y el otro proporciona un contenedor de interfaz público, la parte principal podría contener una referencia débil a la parte del contenedor. Si la parte del contenedor se recolecta como basura, eso significaría ya no había nadie interesado en los datos que se estaban recopilando, por lo que el registrador de cambios debería darse de baja de cualquier evento que reciba).
Dado que es posible que un objeto envíe eventos en nombre de otro objeto, puedo ver alguna utilidad potencial para tener un campo "remitente" que es de tipo Objeto y para que el campo derivado de EventArgs contenga una referencia al objeto que debería ser actuado. Sin embargo, lo inútil del campo "remitente" probablemente esté limitado por el hecho de que no existe una forma clara de que un objeto se dé de baja de un remitente desconocido.
(*) En realidad, una forma más limpia de manejar las cancelaciones de suscripción sería tener un tipo de delegado de multidifusión para las funciones que devuelven booleanos; si una función llamada por dicho delegado devuelve True, el delegado será parcheado para eliminar ese objeto. Esto significaría que los delegados ya no serían realmente inmutables, pero debería ser posible efectuar dicho cambio de manera segura para subprocesos (por ejemplo, anulando la referencia del objeto y haciendo que el código del delegado de multidifusión ignore cualquier referencia de objeto nulo incrustado). En este escenario, un intento de publicar un evento en un objeto eliminado podría manejarse de manera muy limpia, sin importar de dónde provenga el evento.
fuente
Mirando hacia atrás a la blasfemia como la única razón para hacer que el remitente sea un tipo de objeto (si se omiten los problemas de contravarianza en el código VB 2005, que es un error de Microsoft en mi humilde opinión), ¿alguien puede sugerir al menos un motivo teórico para clavar el segundo argumento al tipo EventArgs. Yendo aún más lejos, ¿hay una buena razón para cumplir con las pautas y convenciones de Microsoft en este caso particular?
Tener la necesidad de desarrollar otro contenedor EventArgs para otros datos que queremos pasar dentro del controlador de eventos parece extraño, ¿por qué no podemos pasar directamente esos datos allí? Considere las siguientes secciones de código
[Ejemplo 1]
[Ejemplo 2]
fuente
Con la situación actual (el remitente es un objeto), puede adjuntar fácilmente un método a varios eventos:
Si el remitente fuera genérico, el destino del evento de clic no sería de tipo Botón o Etiqueta, sino de tipo Control (porque el evento está definido en Control). Entonces, algunos eventos en la clase Button tendrían un objetivo de tipo Control, otros tendrían otros tipos de objetivos.
fuente
No creo que haya nada de malo en lo que quieres hacer. En su mayor parte, sospecho que el
object sender
parámetro permanece para continuar admitiendo el código anterior a 2.0.Si realmente desea realizar este cambio para una API pública, es posible que desee considerar la posibilidad de crear su propia clase EvenArgs base. Algo como esto:
Entonces puedes declarar tus eventos así
Y métodos como este:
aún podrá suscribirse.
EDITAR
Esa última línea me hizo pensar un poco ... Debería poder implementar lo que propone sin romper ninguna funcionalidad externa, ya que el tiempo de ejecución no tiene problemas para reducir los parámetros. Todavía me inclinaría hacia la
DataEventArgs
solución (personalmente). Lo haría, sin embargo, sabiendo que es redundante, ya que el remitente se almacena en el primer parámetro y como una propiedad de los argumentos del evento.Un beneficio de seguir con el
DataEventArgs
es que puede encadenar eventos, cambiando el remitente (para representar al último remitente) mientras que EventArgs retiene el remitente original.fuente
Ve a por ello. Para el código no basado en componentes, a menudo simplifico las firmas de eventos para que sean simples
de donde
MyEventType
no heredaEventArgs
. ¿Por qué molestarme, si nunca tengo la intención de utilizar ninguno de los miembros de EventArgs?fuente
event Action<S, T>
,event Action<R, S, T>
etc. Tengo un método de extensión paraRaise
ellos de forma segura :)