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<,>
que GenericEventHandler<,>
. Ya hay genérico EventHandler<>
en BCL que se llama simplemente EventHandler. Entonces, EventHandler es un nombre más común y los delegados admiten sobrecargas de tipos