Signature d'événement dans .NET - Utilisation d'un «expéditeur» typé fort?

106

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 SuppressMessageAttributefait 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

Mike Rosenblum
la source
6
Fais le. (Ne pensez pas que cela justifie une réponse.)
Konrad Rudolph
1
Mes arguments n'étaient pas vraiment dirigés contre vous: bien sûr, vous devriez le faire dans vos propres projets. Ce sont des arguments pour lesquels cela pourrait ne pas fonctionner dans la BCL.
Tommy Carlier
3
Mec, j'aurais aimé que mon projet fasse ça depuis le début, je déteste lancer l'expéditeur.
Matt H
7
Maintenant , C'est une question. Vous voyez, les gars? Pas une de ces questions de la taille d'un tweet , mais une question dont nous apprenons . oh hi this my hom work solve it plz :code dump:
Camilo Martin
3
Une autre suggestion, nommez juste EventHandler<,>que GenericEventHandler<,>. Il existe déjà un générique EventHandler<>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 type
nawfal

Réponses:

25

Il semble que Microsoft a retenu cela car un exemple similaire est maintenant sur MSDN:

Délégués génériques

Bas
la source
2
+1 Ah, excellent. Ils ont bien compris cela. C'est bon. J'espère, cependant, qu'ils en feront un modèle reconnu dans VS IDE, car, comme c'est le cas maintenant, il est plus gênant d'utiliser ce modèle en termes d'IntelliSense, etc.
Mike Rosenblum
13

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.

BGratuit
la source
1
Je suis sûr que c'est exactement la raison. Cependant, maintenant que les nouvelles versions du langage ont une contravariance pour gérer cela, il semble qu'elles devraient pouvoir gérer cela d'une manière rétrocompatible. Les gestionnaires précédents qui utilisent un «objet expéditeur» ne se cassent pas. Mais ce n'est pas vrai pour les langues plus anciennes et peut ne pas être vrai pour certaines langues .NET actuelles, je ne suis pas sûr.
Mike Rosenblum
13

Le Windows Runtime (WinRT) introduit un TypedEventHandler<TSender, TResult>délégué, qui fait exactement ce que vous faites StrongTypedEventHandler<TSender, TResult>, mais apparemment sans la contrainte sur le TResultparamètre de type:

public delegate void TypedEventHandler<TSender, TResult>(TSender sender,
                                                         TResult args);

La documentation MSDN est ici .

Pierre Arnaud
la source
1
Ah, c'est bien de voir qu'il y a des progrès ... Je me demande pourquoi TResult ne se limite pas à hériter de la classe 'EventArgs'. La classe de base 'EventArgs' est essentiellement vide; peut-être s'éloignent-ils de cette restriction?
Mike Rosenblum
Cela pourrait être une erreur de l'équipe de conception; qui sait.
Pierre Arnaud
eh bien, les événements fonctionnent bien sans utilisation EventArgs, c'est juste une convention
Sebastian
3
Il indique spécifiquement dans la documentation TypedEventHandler qui argsseranull 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 type EventArgspouvait 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.
jmcilhinney
1
Cela ne ressemble pas à un oubli. La contrainte a également été supprimée du délégué System.EventHandler <TEventArgs>. referencesource.microsoft.com/#mscorlib/system/…
colton7909
5

Je conteste les déclarations suivantes:

  • Je crois que les anciennes versions de Visual Basic .NET jusqu'à 2005 n'ont pas de covariance déléguée et de contravariance.
  • Je me rends bien compte que cela frôle le blasphème.

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.

Andrew Hare
la source
2
Salut Andrew, merci pour le pouce en l'air! Compte tenu de votre niveau de réputation, cela signifie vraiment beaucoup pour moi ... Sur la question de la covariance / contravariance: si le délégué fourni par l'abonné ne correspond pas exactement à la signature de l'événement de l'éditeur, alors la covariance et la contravariance sont impliquées. C # a eu la covariance déléguée et la contravariance depuis toujours, donc cela semble intrinsèque, mais VB.NET n'avait pas de covariance déléguée et de contravariance jusqu'à .NET 3.0. Par conséquent, VB.NET pour .NET 2.0 et les versions antérieures ne pourraient pas utiliser ce système. (Voir l'exemple de code que j'ai ajouté dans "Edit # 2", ci-dessus.)
Mike Rosenblum
@Mike - Mes excuses, vous avez raison à 100%! J'ai modifié ma réponse pour refléter votre point :)
Andrew Hare
4
Ah, intéressant! Il semble que la covariance / contravariance déléguée fasse partie du CLR, mais (pour des raisons que je ne connais pas) elle n'a pas été exposée par VB.NET avant la version la plus récente. Voici un article de Francesco Balena qui montre comment la variance des délégués peut être obtenue en utilisant Reflection, si elle n'est pas activée par le langage lui-même: dotnet2themax.com/blogs/fbalena/… .
Mike Rosenblum
1
@Mike - Il est toujours intéressant d'apprendre les choses que le CLR prend en charge mais ne sont prises en charge dans aucun des langages .NET.
Andrew Hare
5

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:

[Serializable]
public delegate void TypedEventHandler<in TSender, in TEventArgs>(
    TSender sender,
    TEventArgs e
) where TEventArgs : EventArgs;

Cela semble être la meilleure manière d'avancer compte tenu de l'utilisation du nom TypedEventHandler dans WinRT.

Inverness
la source
Pourquoi ajouter la restriction générique sur TEventArgs? Il a été supprimé de EventHandler <> et TypedEventHandler <,> car cela n'avait pas vraiment de sens.
Mike Marynowski
2

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.

Otávio Décio
la source
Vous avez peut-être raison ... D'un autre côté, je pense que c'est juste un "standard", et peut-être pas du tout un problème technique. Autrement dit, cette capacité pourrait être présente dans tous les langages .NET actuels, je ne sais pas. Je sais que C # et VB.NET peuvent gérer cela. Cependant, je ne sais pas dans quelle mesure cela fonctionne dans tous les langages .NET actuels ... Mais comme cela fonctionne en C # et VB.NET, et que tout le monde ici est si favorable, je pense que je suis très susceptible de le faire. :-)
Mike Rosenblum
2

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.

supercat
la source
2

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]

public delegate void ConnectionEventHandler(Server sender, Connection connection);

public partial class Server
{
    protected virtual void OnClientConnected(Connection connection)
    {
        if (ClientConnected != null) ClientConnected(this, connection);
    }

    public event ConnectionEventHandler ClientConnected;
}

[Exemple 2]

public delegate void ConnectionEventHandler(object sender, ConnectionEventArgs e);

public class ConnectionEventArgs : EventArgs
{
    public Connection Connection { get; private set; }

    public ConnectionEventArgs(Connection connection)
    {
        this.Connection = connection;
    }
}

public partial class Server
{
    protected virtual void OnClientConnected(Connection connection)
    {
        if (ClientConnected != null) ClientConnected(this, new ConnectionEventArgs(connection));
    }

    public event ConnectionEventHandler ClientConnected;
}
Lu4
la source
2
Oui, créer une classe distincte qui hérite de System.EventArgs peut sembler peu intuitif et représente un travail supplémentaire, mais il y a une très bonne raison à cela. Si vous n'avez jamais besoin de changer votre code, alors votre approche est bonne. Mais la réalité est que vous devrez peut-être augmenter la fonctionnalité de l'événement dans une version future et ajouter des propriétés aux arguments d'événement. Dans votre scénario, vous devrez ajouter des surcharges supplémentaires ou des paramètres facultatifs à la signature du gestionnaire d'événements. C'est une approche utilisée dans VBA et l'héritage VB 6.0, qui est réalisable, mais un peu moche en pratique.
Mike Rosenblum
1
En héritant d'EventArgs, cependant, une version future pourrait hériter de votre ancienne classe d'arguments d'événement et l'augmenter. Tous les anciens appelants peuvent toujours fonctionner exactement tels quels, en opérant sur la classe de base de votre nouvelle classe d'arguments d'événement. Très propre. Plus de travail pour vous, mais plus propre pour tous les appelants qui dépendent de votre bibliothèque.
Mike Rosenblum
Il n'a même pas besoin d'en hériter, vous pouvez simplement ajouter la fonctionnalité supplémentaire directement dans votre classe d'arguments d'événement et cela continuera à fonctionner correctement. Cela dit, la restriction d'épinglage d'arguments à eventargs a été supprimée car elle n'avait pas beaucoup de sens pour de nombreux scénarios, c'est-à-dire. lorsque vous savez que vous n'aurez jamais besoin d'étendre les fonctionnalités d'un événement particulier ou lorsque tout ce dont vous avez besoin est un type de valeur arg dans des applications très sensibles aux performances.
Mike Marynowski
1

Avec la situation actuelle (l'expéditeur est un objet), vous pouvez facilement attacher une méthode à plusieurs événements:

button.Click += ClickHandler;
label.Click += ClickHandler;

void ClickHandler(object sender, EventArgs e) { ... }

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.

Tommy Carlier
la source
2
Tommy, tu peux faire exactement la même chose avec le système que je propose. Vous pouvez toujours utiliser un gestionnaire d'événements standard qui a un paramètre «object sender» pour gérer ces événements de type fort. (Voir l'exemple de code que j'ai maintenant ajouté au message d'origine.)
Mike Rosenblum
Oui, je suis d'accord, c'est la bonne chose à propos des événements .NET standard, acceptés!
Lu4 du
1

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 senderparamè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:

public class DataEventArgs<TSender, TData> : EventArgs
{
    private readonly TSender sender, TData data;

    public DataEventArgs(TSender sender, TData data)
    {
        this.sender = sender;
        this.data = data;
    }

    public TSender Sender { get { return sender; } }
    public TData Data { get { return data; } }
}

Ensuite, vous pouvez déclarer vos événements comme ceci

public event EventHandler<DataEventArgs<MyClass, int>> SomeIndexSelected;

Et des méthodes comme celle-ci:

private void HandleSomething(object sender, EventArgs e)

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 leDataEventArgs 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 ' DataEventArgsest 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.

Michael Meadows
la source
Hey Michael, c'est une alternative assez intéressante. Je l'aime. Comme vous l'avez mentionné, cependant, il est redondant que le paramètre «expéditeur» soit effectivement passé deux fois. Une approche similaire est discutée ici: stackoverflow.com/questions/809609/… , et le consensus semble être qu'elle est trop non standard. C'est pourquoi j'ai hésité à suggérer ici l'idée de «l'expéditeur» fortement typée. (Cela semble bien accueilli, donc je suis content.)
Mike Rosenblum
1

Fonce. Pour le code non basé sur des composants, je simplifie souvent les signatures d'événements pour être simplement

public event Action<MyEventType> EventName

d'où MyEventTypen'hérite pas EventArgs. Pourquoi s'embêter, si je n'ai jamais l'intention d'utiliser l'un des membres d'EventArgs.

Scott Weinstein
la source
1
Se mettre d'accord! Pourquoi devrions-nous nous sentir singes?
Lu4 du
1
+ 1-ed, c'est ce que j'utilise aussi. Parfois, la simplicité l'emporte! Ou même event Action<S, T>, event Action<R, S, T>etc. J'ai une méthode d'extension pour Raiseeux en toute sécurité :)
nawfal