Signature d'événement dans .NET — à l'Aide d'une Forte Tapé "Expéditeur"?
J'ai pleinement conscience que ce que je propose n'a pas suivi la .NET des lignes directrices, et, par conséquent, est probablement une mauvaise idée pour cette seule raison. Cependant, j'aimerais réfléchir à ce à partir de deux perspectives possibles:
(1) dois-je envisager d'utiliser cela pour mon propre travail de développement, qui est de 100% pour des fins internes.
(2) Est-ce un concept que le cadre designers pourrait envisager de modifier ou de mettre à jour?
Je pense à propos de l'utilisation d'un événement de signature qui utilise une forte tapé "expéditeur", au lieu de taper comme "objet", qui est le courant .NET design pattern. C'est, au lieu d'utiliser un événement de signature qui ressemble à ceci:
class Publisher
{
public event EventHandler<PublisherEventArgs> SomeEvent;
}
Je suis envisage d'utiliser un événement de signature qui utilise un fort typée "expéditeur" du paramètre, comme suit:
D'abord, de définir 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 faisant usage de la StrongTypedEventHandler
, nous imposons que la TEventArgs dérive de System.EventArgs
.
Suivant, à titre d'exemple, nous pouvons faire usage de la StrongTypedEventHandler dans une publication de classe comme suit:
class Publisher
{
public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;
protected void OnSomeEvent()
{
if (SomeEvent != null)
{
SomeEvent(this, new PublisherEventArgs(...));
}
}
}
L'arrangement ci-dessus serait de permettre aux abonnés d'utiliser un fort typée gestionnaire d'événements qui ne nécessitent pas de casting:
class Subscriber
{
void SomeEventHandler(Publisher sender, PublisherEventArgs e)
{
if (sender.Name == "John Smith")
{
//...
}
}
}
Je n'ai pleinement conscience que cela rompt avec la norme .NET en cas de manipulation de modèle; cependant, gardez à l'esprit que la contravariance permettant à un abonné d'utiliser un événement traditionnel de la manipulation de la signature si vous le souhaitez:
class Subscriber
{
void SomeEventHandler(object sender, PublisherEventArgs e)
{
if (((Publisher)sender).Name == "John Smith")
{
//...
}
}
}
Qui est, si un gestionnaire d'événement nécessaire de s'abonner à des événements disparates (ou peut-être inconnu) types d'objet, le gestionnaire pourrait le type de l '"expéditeur" paramètre "objet" afin de gérer l'intégralité du potentiel de l'expéditeur objets.
Autres que la rupture de la convention (qui est quelque chose que je ne prends pas à la légère, croyez-moi) je ne peux pas penser à tout les inconvénients de cette.
Il peut y avoir quelques CLS de la conformité des problèmes ici. Ce n'est exécuté dans Visual Basic .NET 2008 100% de fines (j'ai testé), mais je crois que les versions antérieures de Visual Basic .NET par le biais de 2005 n'ont pas de délégué de la covariance et la contravariance. [Edit: depuis, j'ai testé, et c'est confirmé: VB.NET 2005 et ci-dessous ne peut pas gérer cela, mais VB.NET 2008 est de 100% de fines. Voir la section "Edit #2", ci-dessous.] Il y a peut être d'autres .NET les langues qui ont aussi un problème avec cela, je ne peux pas en être sûr.
Mais je ne me vois pas le développement de toute autre langue que le C# ou Visual Basic .NET, et je n'ai pas l'esprit de la restriction de C# et VB.NET pour .NET Framework 3.0 et au-dessus. (Je ne pouvais pas imaginer revenir à 2.0 à ce point, pour être honnête.)
Une autre personne peut penser à un problème avec cela? Ou est-ce tout simplement rompre avec la convention, si bien qu'il fait de l'estomac des gens tourner?
Voici quelques liens que j'ai trouvé:
(1) La Conception de l'événement lignes Directrices [MSDN 3.5]
(3) Signature d'événement motif dans .net [StackOverflow 2008]
Je suis intéressé par n'importe qui et tout le monde est d'avis sur cette...
Merci d'avance,
Mike
Edit #1: C'est en réponse à Tommy Carlier post :
Ici un exemple qui montre que les deux fort typée gestionnaires d'événements et la norme actuelle des gestionnaires d'événements que l'utilisation d'un objet expéditeur " paramètre peut co-exister avec cette approche. Vous pouvez copier-coller dans le code et de lui donner une course:
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: C'est en réponse à Andrew Hare, déclaration concernant la covariance et la contravariance et comment il s'applique ici. Les délégués dans le langage C# ont eu la covariance et la contravariance pendant si longtemps qu'elle se sent juste "intrinsèque", mais il ne l'est pas. Il pourrait même être quelque chose qui est activé dans le CLR, je ne sais pas, mais Visual Basic .NET n'a pas obtenu la covariance et la contravariance capacité de ses délégués, jusqu'à ce que l' .NET Framework 3.0 (VB.NET 2008). Et comme un résultat, Visual Basic.NET pour .NET 2.0 et au-dessous ne serait pas en mesure d'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 s'exécuter à 100% de fines. Mais j'ai maintenant testé sur VB.NET 2005, juste pour être sûr, et il ne compile pas, en déclarant:
Méthode " Public Sub
SomeEventHandler(sender as Object, e
Comme
vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)'
ne pas avoir la même signature que
délégué Délégué Sous
StrongTypedEventHandler(De TSender,
TEventArgs En Tant Que Système.EventArgs)(expéditeur
En tant qu'Éditeur, e
PublisherEventArgs)'
Fondamentalement, les délégués sont invariantes dans VB.NET les versions 2005 et ci-dessous. J'ai effectivement pensé à cette idée il y a quelques années, mais VB.NET s'incapacité à faire face à ce qui me dérangeait beaucoup... Mais j'ai maintenant déménagé solidement à C#, et VB.NET peut maintenant en occuper, donc, également, d'où ce post.
Edit: Mise À Jour #3
Ok, j'ai été en utilisant ce assez de succès pour un certain temps maintenant. C'est vraiment un très bon système. J'ai décidé de nommer mon "StrongTypedEventHandler" comme "GenericEventHandler", définis comme suit:
[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
TSender sender,
TEventArgs e
)
where TEventArgs : EventArgs;
Autre que ce changement de nom, j'ai mis en place exactement comme discuté ci-dessus.
Il ne voyage plus de FxCop règle CA1009, qui stipule:
"Par convention .NET événements ont deux
les paramètres qui spécifient l'événement
de l'expéditeur et les données de l'événement. Gestionnaire d'événements
les signatures doivent suivre ce formulaire:
void MyEventHandler( object sender,
EventArgs e). L '"expéditeur" paramètre
est toujours de type System.Objet, même
si il est possible d'employer un plus
type spécifique. Le 'e' le paramètre est
toujours de type System.EventArgs.
Les événements qui ne fournissent pas les données de l'événement
doit utiliser le Système.Gestionnaire d'événements
type de délégué. Les gestionnaires d'événements de retour
nul, de sorte qu'ils peuvent envoyer chaque événement
de multiples méthodes cible. Toute valeur
renvoyée par la cible serait perdu
après le premier appel."
Bien sûr, nous savons tous cela, et ne respectent pas les règles de toute façon. (Tous les gestionnaires d'événements peuvent utiliser la norme object Sender' dans leur signature si l'on préfère, en tout cas c'est un non-modification de rupture.)
Ainsi, l'utilisation d'un SuppressMessageAttribute
fait le truc:
[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
J'espère que cette approche devient la norme à un certain moment dans l'avenir. Il fonctionne vraiment très bien.
Merci à tous pour vos avis les gars, j'ai vraiment l'apprécier...
Mike
- Le faire. (Ne pense pas que cela justifie une réponse.)
- Mes arguments n'étaient pas vraiment pointé sur vous: bien sûr, vous devriez le faire dans vos propres projets. Ils ont été des arguments pourquoi il ne pourrait pas travailler dans la BCL.
- Gotcha, merci Tommy. (Mais je pense qu'il serait de travailler largement, au moins avec tout .NET langue qui avait délégué la contravariance.)
- L'homme, je souhaite que mon projet avait fait depuis le début, je déteste le casting de l'expéditeur.
- Maintenant CE est une question. Voir, les gens? Pas un seul de ces tweet taille
oh hi this my hom work solve it plz :code dump:
questions, mais une question nous apprendre. - Une autre suggestion, juste de nom, il
EventHandler<,>
queGenericEventHandler<,>
. Il y a déjà des génériquesEventHandler<>
dans la BCL qui est nommé simplement Gestionnaire d'événements. Donc, ce Gestionnaire est un nom plus commun et des délégués prend en charge le type de surcharges
Vous devez vous connecter pour publier un commentaire.
Il semble que Microsoft a pris cela comme un exemple similaire est maintenant sur MSDN:
Générique Délégués
Ce que vous proposez ne faire beaucoup de sens en fait, et je me demande si cela est une de ces choses qui est tout simplement la façon dont il est, car il a été conçu à l'origine avant de génériques, ou si il y a une vraie raison pour cela.
Le Windows Runtime (WinRT) introduit un
TypedEventHandler<TSender, TResult>
délégué, qui fait exactement ce que votreStrongTypedEventHandler<TSender, TResult>
fait, mais apparemment sans la contrainte sur laTResult
paramètre de type:La documentation MSDN est ici.
EventArgs
, c'est juste une convention choseargs
seranull
si il n'y a pas de données d'événement, de sorte qu'il n'apparaît qu'ils sont loin de l'aide est essentiellement un objet vide par défaut. Je suppose que l'idée de départ était qu'une méthode avec un deuxième paramètre de typeEventArgs
pourrait traiter tous les cas, puisque les types serait toujours compatibles. Ils sont probablement réaliser maintenant que le fait d'être en mesure de gérer plusieurs événements différents avec une méthode n'est pas du tout important.e
contraint deEventArgs
, pour permettre au développeur de faire tout ce qui est plus significatif dans le contexte.J'en désaccord avec les énoncés suivants:
tout d'Abord, rien de ce que vous avez fait ici n'a rien à voir avec la covariance ou de contravariance.(Edit: L'instruction précédente est fausse, pour plus d'informations, veuillez consulter La Covariance et la Contravariance dans Délégués) Cette solution fonctionne très bien dans toutes les versions CLR 2.0 et (bien évidemment ce sera pas travailler dans un CLR 1.0 de l'application, car il utilise les génériques).Deuxièmement, je suis en total désaccord que votre idée, qui frise le "blasphème", car c'est une merveilleuse idée.
J'ai pris un coup d'oeil à la façon dont cela a été traité avec le nouveau WinRT et sur la base d'autres avis ici, et s'est finalement porté sur le faire comme ceci:
Ce qui semble être la meilleure voie à suivre compte tenu de l'utilisation du nom TypedEventHandler dans WinRT.
Je pense que c'est une excellente idée et MME peut tout simplement ne pas avoir le temps ou l'intérêt d'investir dans la fabrication de ce mieux que par exemple quand ils ont déménagé de liste de tableaux génériques à base de listes.
De ce que je comprends, le champ "Expéditeur" est toujours censé faire référence à l'objet qui contient l'inscription à l'événement. Si j'avais tenait qu'à moi, il y aurait aussi un champ contenant les informations suffisantes pour se désabonner d'un événement si nécessaire(*) (prenons, par exemple, d'un changement d'enregistreur de frappe qui s'abonne à la collection de changement " des événements; il contient deux parties, l'une qui fait tout le boulot et détient les données réelles, et l'autre, qui fournit une interface publique wrapper, la partie principale pourrait tenir une référence faible de l'enveloppe de la partie. Si le wrapper de la partie obtient le garbage collector, ça voudrait dire que il n'y avait plus personne intéressée dans les données qui ont été recueillies, et le changement de logger doit donc se désinscrire de tous les cas, il reçoit).
Puisqu'il est possible qu'un objet peut envoyer des événements au nom d'un autre objet, je peux voir certains de l'utilité potentielle pour avoir un champ "expéditeur" qui est de type Objet, et pour avoir la EventArgs dérivés champ contient une référence à l'objet qui doivent être traitées. Le usefuless de le champ "expéditeur", cependant, est probablement limité par le fait qu'il n'y a pas de moyen propre d'un objet pour vous désabonner d'un expéditeur inconnu.
(*) En fait, d'une façon plus propre de la manipulation des désabonnements serait d'avoir un délégué multicast type de fonctions qui retournent Booléenne; si une fonction appelée par un délégué renvoie la valeur True, le délégué être patché pour supprimer cet objet. Cela signifierait que les délégués ne serait plus vraiment immuable, mais il devrait être possible d'effectuer un tel changement dans le thread de manière sûre (par exemple, par micropression de l'objet de référence et ayant le délégué multicast code ignorer intégré null références de l'objet). En vertu de ce scénario, une tentative de publier et de cas éliminés de l'objet peut être manipulé très proprement, peu importe l'endroit où l'événement est venu.
Regarder en arrière pour blasphème que la seule raison de faire de l'expéditeur d'un type d'objet (si d'omettre des problèmes avec la contravariance en VB 2005 code, qui est un de Microsoft gaffe à mon humble avis), quelqu'un peut-il suggérer au moins théorique de la motivation pour le clouage de la deuxième argument de type EventArgs. Il va encore plus loin, est-il une bonne raison pour être conforme avec les recommandations de Microsoft et de conventions dans ce cas particulier?
Avoir besoin de développer un autre EventArgs wrapper pour une autre les données que nous voulons passer à l'intérieur de gestionnaire d'événement qui semble étrange, pourquoi ne pas directement passer que les données à ce niveau. Examiner les sections de code suivant
[Exemple 1]
[Exemple 2]
Avec la situation actuelle (de l'expéditeur sur l'objet), vous pouvez facilement joindre une méthode à de multiples événements:
Si l'expéditeur serait générique, la cible de la sur-l'événement ne serait pas de type Bouton ou une Étiquette, mais de type de Contrôle (parce que l'événement est défini sur le Contrôle). De sorte que certains événements sur le Bouton-de la classe ont un objectif de Contrôle de type, d'autres ont d'autres types de cibles.
Je ne pense pas qu'il ya quelque chose de mal avec ce que vous voulez faire. Pour la plupart, je pense que le
object sender
paramètre reste afin de continuer à soutenir pré 2.0 code.Si vous voulez vraiment faire ce changement pour une API publique, vous pourriez envisager de créer votre propre base de EvenArgs classe. Quelque chose comme ceci:
Vous pouvez déclarer vos événements comme cette
Et des méthodes comme ceci:
sera toujours en mesure de vous abonner.
MODIFIER
Que la dernière ligne m'a fait réfléchir un peu... en fait, Vous devez être en mesure de mettre en œuvre ce que vous proposez, sans rompre tout à l'extérieur de la fonctionnalité depuis le runtime n'a pas de problème de passer des paramètres. Je voudrais encore penchez-vous vers l'
DataEventArgs
solution (personnellement). Je voudrais le faire, mais le fait de savoir qu'il est redondant, puisque l'expéditeur est stocké dans le premier paramètre, et en tant que propriété de l'événement args.Un avantage de coller avec le
DataEventArgs
est que vous pouvez la chaîne d'événements, la modification de l'expéditeur (pour représenter la dernière de l'expéditeur), tandis que le EventArgs conserve l'expéditeur d'origine.Aller pour elle. Pour les non basée sur des composants de code, j'ai souvent simplifier Cas des signatures pour être simplement
où
MyEventType
n'hérite pas deEventArgs
. Pourquoi s'embêter, si je n'ai jamais l'intention d'utiliser l'un des membres de EventArgs.event Action<S, T>
,event Action<R, S, T>
etc. J'ai une méthode d'extension pourRaise
leur fil en toute sécurité 🙂