Je me rends parfaitement compte que ce que je propose ne suit pas les directives .NET et, par conséquent, est probablement une mauvaise idée pour cette seule raison. Cependant, je voudrais considérer cela sous deux angles possibles:
(1) Dois-je envisager de l'utiliser pour mon propre travail de développement, qui est à 100% à des fins internes.
(2) Est-ce un concept que les concepteurs du cadre pourraient envisager de modifier ou de mettre à jour?
Je pense utiliser une signature d'événement qui utilise un «expéditeur» fortement typé, au lieu de le taper comme «objet», qui est le modèle de conception actuel de .NET. Autrement dit, au lieu d'utiliser une signature d'événement standard qui ressemble à ceci:
class Publisher
{
public event EventHandler<PublisherEventArgs> SomeEvent;
}
J'envisage d'utiliser une signature d'événement qui utilise un paramètre `` expéditeur '' de type fort, comme suit:
Tout d'abord, définissez un "StrongTypedEventHandler":
[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
Ce n'est pas si différent d'une Action <TSender, TEventArgs>, mais en utilisant le StrongTypedEventHandler
, nous imposons que le TEventArgs dérive System.EventArgs
.
Ensuite, à titre d'exemple, nous pouvons utiliser StrongTypedEventHandler dans une classe de publication comme suit:
class Publisher
{
public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;
protected void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent(this, new PublisherEventArgs(...));
}
}
}
L'agencement ci-dessus permettrait aux abonnés d'utiliser un gestionnaire d'événements à fort typage qui ne nécessitait pas de diffusion:
class Subscriber
{
void SomeEventHandler(Publisher sender, PublisherEventArgs e)
{
if (sender.Name == "John Smith")
{
// ...
}
}
}
Je me rends parfaitement compte que cela rompt avec le modèle standard de gestion des événements .NET; cependant, gardez à l'esprit que la contravariance permettrait à un abonné d'utiliser une signature de gestion d'événements traditionnelle s'il le souhaite:
class Subscriber
{
void SomeEventHandler(object sender, PublisherEventArgs e)
{
if (((Publisher)sender).Name == "John Smith")
{
// ...
}
}
}
Autrement dit, si un gestionnaire d'événements avait besoin de s'abonner à des événements de types d'objets disparates (ou peut-être inconnus), le gestionnaire pourrait taper le paramètre «expéditeur» comme «objet» afin de gérer toute l'étendue des objets expéditeurs potentiels.
À part briser les conventions (ce que je ne prends pas à la légère, croyez-moi), je ne peux pas penser à des inconvénients à cela.
Il peut y avoir des problèmes de conformité CLS ici. Cela fonctionne à 100% dans Visual Basic .NET 2008 (j'ai testé), mais je crois que les anciennes versions de Visual Basic .NET jusqu'en 2005 n'ont pas de covariance déléguée et de contravariance. [Edit: J'ai depuis testé ceci, et c'est confirmé: VB.NET 2005 et ci-dessous ne peuvent pas gérer cela, mais VB.NET 2008 est 100% bien. Voir «Édition # 2», ci-dessous.] Il peut y avoir d'autres langages .NET qui ont également un problème avec cela, je ne peux pas être sûr.
Mais je ne me vois pas développer pour un langage autre que C # ou Visual Basic .NET, et cela ne me dérange pas de le limiter à C # et VB.NET pour .NET Framework 3.0 et supérieur. (Je ne pouvais pas imaginer revenir à 2.0 à ce stade, pour être honnête.)
Quelqu'un d'autre peut-il penser à un problème avec cela? Ou est-ce que cela rompt tellement avec les conventions que cela fait tourner l'estomac des gens?
Voici quelques liens connexes que j'ai trouvés:
(1) Directives de conception d'événements [MSDN 3.5]
(2) C # simple Event Raising - en utilisant «expéditeur» vs EventArgs personnalisés [StackOverflow 2009]
(3) Modèle de signature d'événement dans .net [StackOverflow 2008]
Je suis intéressé par l'opinion de tout le monde à ce sujet ...
Merci d'avance,
Mike
Edit # 1: Ceci est en réponse au message de Tommy Carlier :
Voici un exemple de travail complet qui montre que les gestionnaires d'événements à typage fort et les gestionnaires d'événements standard actuels qui utilisent un paramètre 'object sender' peuvent coexister avec cette approche. Vous pouvez copier-coller le code et lui donner une exécution:
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();
}
}
}
Edit # 2: Ceci est en réponse à la déclaration d'Andrew Hare concernant la covariance et la contravariance et comment cela s'applique ici. Les délégués dans le langage C # ont eu la covariance et la contravariance pendant si longtemps que cela semble juste "intrinsèque", mais ce n'est pas le cas. C'est peut-être même quelque chose qui est activé dans le CLR, je ne sais pas, mais Visual Basic .NET n'a pas obtenu de capacité de covariance et de contravariance pour ses délégués avant le .NET Framework 3.0 (VB.NET 2008). Et par conséquent, Visual Basic.NET pour .NET 2.0 et les versions antérieures ne pourraient pas utiliser cette approche.
Par exemple, l'exemple ci-dessus peut être traduit en VB.NET comme suit:
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 peut l'exécuter à 100%. Mais je l'ai maintenant testé sur VB.NET 2005, juste pour être sûr, et il ne se compile pas, déclarant:
La méthode 'Public Sub SomeEventHandler (sender As Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' n'a pas la même signature que le délégué 'Delegate Sub StrongTypedEventHandler (Of TSender, TEventArgs As System.EventArgs) (sender As PublisherA, eventArgs) '
Fondamentalement, les délégués sont invariants dans les versions VB.NET 2005 et antérieures. J'ai en fait pensé à cette idée il y a quelques années, mais l'incapacité de VB.NET à gérer cela m'a dérangé ... Mais je suis maintenant passé fermement à C #, et VB.NET peut maintenant le gérer, donc, eh bien, par conséquent ce post.
Edit: mise à jour n ° 3
Ok, j'utilise cela avec succès depuis un moment maintenant. C'est vraiment un bon système. J'ai décidé de nommer mon "StrongTypedEventHandler" comme "GenericEventHandler", défini comme suit:
[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
En dehors de ce changement de nom, je l'ai implémenté exactement comme indiqué ci-dessus.
Il déclenche la règle FxCop CA1009, qui stipule:
"Par convention, les événements .NET ont deux paramètres qui spécifient l'expéditeur de l'événement et les données de l'événement. Les signatures du gestionnaire d'événements doivent suivre cette forme: void MyEventHandler (expéditeur de l'objet, EventArgs e). Le paramètre 'sender' est toujours de type System.Object, même s'il est possible d'employer un type plus spécifique. Le paramètre «e» est toujours de type System.EventArgs. Les événements qui ne fournissent pas de données d'événement doivent utiliser le type de délégué System.EventHandler. Les gestionnaires d'événements renvoient void pour pouvoir envoyer chaque événement à plusieurs méthodes cibles. Toute valeur renvoyée par une cible serait perdue après le premier appel. "
Bien sûr, nous savons tout cela et enfreignons les règles de toute façon. (Tous les gestionnaires d'événements peuvent utiliser le standard 'object Sender' dans leur signature s'ils le souhaitent dans tous les cas - il s'agit d'un changement sans rupture.)
Donc, l'utilisation de a SuppressMessageAttribute
fait l'affaire:
[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
J'espère que cette approche deviendra la norme à un moment donné dans le futur. Cela fonctionne vraiment très bien.
Merci pour tous vos avis les gars, je l'apprécie vraiment ...
Mike
oh hi this my hom work solve it plz :code dump:
EventHandler<,>
queGenericEventHandler<,>
. Il existe déjà un génériqueEventHandler<>
dans BCL qui s'appelle simplement EventHandler. Donc EventHandler est un nom plus courant et les délégués prennent en charge les surcharges de typeRéponses:
Il semble que Microsoft a retenu cela car un exemple similaire est maintenant sur MSDN:
Délégués génériques
la source
Ce que vous proposez a en fait beaucoup de sens, et je me demande simplement si c'est une de ces choses qui est simplement comme ça parce qu'elle a été conçue à l'origine avant les génériques, ou s'il y a une vraie raison à cela.
la source
Le Windows Runtime (WinRT) introduit un
TypedEventHandler<TSender, TResult>
délégué, qui fait exactement ce que vous faitesStrongTypedEventHandler<TSender, TResult>
, mais apparemment sans la contrainte sur leTResult
paramètre de type:La documentation MSDN est ici .
la source
EventArgs
, c'est juste une conventionargs
seranull
s'il n'y a pas de données d'événement, il semble donc qu'ils s'éloignent de l'utilisation d'un objet essentiellement vide par défaut. Je suppose que l'idée originale était qu'une méthode avec un deuxième paramètre de typeEventArgs
pouvait gérer n'importe quel événement car les types seraient toujours compatibles. Ils se rendent probablement compte maintenant qu'il n'est pas si important de pouvoir gérer plusieurs événements différents avec une seule méthode.Je conteste les déclarations suivantes:
Tout d'abord, rien de ce que vous avez fait ici n'a rien à voir avec la covariance ou la contravariance.( Modifier: La déclaration précédente est fausse, pour plus d'informations, veuillez consulter Covariance et Contravariance dans les délégués ) Cette solution fonctionnera très bien dans toutes les versions CLR 2.0 et supérieures (évidemment, cela ne fonctionnera pas dans une application CLR 1.0 car elle utilise des génériques).Deuxièmement, je ne suis pas du tout d'accord avec le fait que votre idée frise le "blasphème" car c'est une idée merveilleuse.
la source
J'ai jeté un coup d'œil à la façon dont cela a été géré avec le nouveau WinRT et sur la base d'autres opinions ici, et j'ai finalement décidé de le faire comme ceci:
Cela semble être la meilleure manière d'avancer compte tenu de l'utilisation du nom TypedEventHandler dans WinRT.
la source
Je pense que c'est une excellente idée et que les États membres n'ont peut-être tout simplement pas le temps ou l'intérêt d'investir pour améliorer cela, par exemple lorsqu'ils sont passés de ArrayList à des listes génériques.
la source
D'après ce que j'ai compris, le champ "Expéditeur" est toujours censé faire référence à l'objet qui contient l'abonnement à l'événement. Si j'avais mes druthers, il y aurait aussi un champ contenant des informations suffisantes pour désabonner un événement si cela devenait nécessaire (*) (considérez, par exemple, un enregistreur de changements qui s'abonne à des événements 'collection-changed'; il contient deux parties , dont l'un fait le travail réel et contient les données réelles, et l'autre fournit un wrapper d'interface publique, la partie principale peut contenir une référence faible à la partie wrapper. Si la partie wrapper est récupérée, cela signifierait personne n'était plus intéressé par les données collectées, et l'enregistreur de modifications devrait donc se désinscrire de tout événement qu'il reçoit).
Puisqu'il est possible qu'un objet envoie des événements au nom d'un autre objet, je peux voir une utilité potentielle pour avoir un champ "expéditeur" qui est de type Object, et pour que le champ dérivé d'EventArgs contienne une référence à l'objet qui devrait être agi. L'utilité du champ "sender", cependant, est probablement limitée par le fait qu'il n'y a pas de moyen propre pour un objet de se désinscrire d'un expéditeur inconnu.
(*) En fait, une façon plus propre de gérer les désabonnements serait d'avoir un type de délégué de multidiffusion pour les fonctions qui retournent Boolean; si une fonction appelée par un tel délégué renvoie True, le délégué serait corrigé pour supprimer cet objet. Cela signifierait que les délégués ne seraient plus vraiment immuables, mais il devrait être possible d'effectuer un tel changement de manière thread-safe (par exemple en annulant la référence d'objet et en faisant ignorer au code délégué de multidiffusion toutes les références d'objet nul incorporées). Dans ce scénario, une tentative de publication et d'événement sur un objet supprimé pouvait être gérée très proprement, quelle que soit l'origine de l'événement.
la source
En repensant au blasphème comme seule raison de faire de l'expéditeur un type d'objet (si vous omettez les problèmes de contravariance dans le code VB 2005, qui est une erreur de Microsoft à mon humble avis), quelqu'un peut-il suggérer au moins un motif théorique pour clouer le deuxième argument au type EventArgs. Pour aller encore plus loin, y a-t-il une bonne raison de se conformer aux directives et conventions de Microsoft dans ce cas particulier?
Avoir besoin de développer un autre wrapper EventArgs pour d'autres données que nous voulons passer à l'intérieur du gestionnaire d'événements semble étrange, pourquoi ne peut-il pas passer directement ces données là-bas. Considérez les sections de code suivantes
[Exemple 1]
[Exemple 2]
la source
Avec la situation actuelle (l'expéditeur est un objet), vous pouvez facilement attacher une méthode à plusieurs événements:
Si l'expéditeur était générique, la cible de l'événement de clic ne serait pas de type Button ou Label, mais de type Control (car l'événement est défini sur Control). Ainsi, certains événements de la classe Button auraient une cible de type Control, d'autres auraient d'autres types de cibles.
la source
Je ne pense pas qu'il y ait quelque chose de mal dans ce que vous voulez faire. Pour la plupart, je soupçonne que le
object sender
paramètre reste afin de continuer à prendre en charge le code pré 2.0.Si vous souhaitez vraiment apporter cette modification pour une API publique, vous pouvez envisager de créer votre propre classe EvenArgs de base. Quelque chose comme ça:
Ensuite, vous pouvez déclarer vos événements comme ceci
Et des méthodes comme celle-ci:
pourra toujours souscrire.
ÉDITER
Cette dernière ligne m'a fait réfléchir un peu ... Vous devriez en fait être capable d'implémenter ce que vous proposez sans casser aucune fonctionnalité extérieure puisque le runtime n'a aucun problème pour réduire les paramètres. Je me pencherais toujours vers le
DataEventArgs
solution (personnellement). Je le ferais, cependant sachant que c'est redondant, puisque l'expéditeur est stocké dans le premier paramètre et en tant que propriété de l'événement args.L'un des avantages de s'en tenir à l '
DataEventArgs
est que vous pouvez enchaîner les événements, en changeant l'expéditeur (pour représenter le dernier expéditeur) tandis que EventArgs conserve l'expéditeur d'origine.la source
Fonce. Pour le code non basé sur des composants, je simplifie souvent les signatures d'événements pour être simplement
d'où
MyEventType
n'hérite pasEventArgs
. Pourquoi s'embêter, si je n'ai jamais l'intention d'utiliser l'un des membres d'EventArgs.la source
event Action<S, T>
,event Action<R, S, T>
etc. J'ai une méthode d'extension pourRaise
eux en toute sécurité :)