107 votes

Signature d'événement dans .NET -- Utilisation d'un "expéditeur" fortement typé ?

Je suis pleinement conscient que ce que je propose ne respecte pas les directives .NET et que, par conséquent, c'est probablement une mauvaise idée pour cette seule raison. Cependant, j'aimerais envisager la question sous deux angles différents :

(1) Devrais-je envisager de l'utiliser pour mon propre travail de développement, qui est à 100% à des fins internes.

(2) S'agit-il d'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", ce 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 la fonction StrongTypedEventHandler nous imposons que les TEventArgs dérivent de System.EventArgs .

Ensuite, à titre d'exemple, nous pouvons utiliser le 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'arrangement ci-dessus permettrait aux abonnés d'utiliser un gestionnaire d'événement de type fort qui ne nécessiterait pas de moulage :

class Subscriber
{
    void SomeEventHandler(Publisher sender, PublisherEventArgs e)
    {           
        if (sender.Name == "John Smith")
        {
            // ...
        }
    }
}

Je suis tout à fait conscient que cela rompt avec le modèle standard de traitement des événements de .NET ; cependant, gardez à l'esprit que la contravariance permettrait à un abonné d'utiliser une signature traditionnelle de traitement des événements s'il le souhaite :

class Subscriber
{
    void SomeEventHandler(object sender, PublisherEventArgs e)
    {           
        if (((Publisher)sender).Name == "John Smith")
        {
            // ...
        }
    }
}

En d'autres termes, si un gestionnaire d'événements doit s'abonner à des événements provenant de types d'objets disparates (ou peut-être inconnus), il peut saisir le paramètre "sender" comme "object" afin de gérer toute la gamme des objets expéditeurs potentiels.

Mis à part le fait de briser les conventions (ce que je ne prends pas à la légère, croyez-moi), je ne vois pas d'inconvénients à cela.

Il peut y avoir des problèmes de conformité avec le CLS. Cela fonctionne dans Visual Basic .NET 2008 100% bien (j'ai testé), mais je crois que les anciennes versions de Visual Basic .NET jusqu'à 2005 n'ont pas la covariance et la contravariance des délégués. [Je l'ai testé depuis, et c'est confirmé : VB.NET 2005 et inférieur ne peut pas gérer cela, mais VB.NET 2008 est 100% correct. Voir "Edit #2", ci-dessous]. Il se peut que d'autres langages .NET aient également un problème avec cela, je ne peux pas en être sûr.

Mais je ne me vois pas développer pour un autre langage 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 plus. (Je ne pourrais pas imaginer revenir à 2.0 à ce stade, pour être honnête).

Quelqu'un d'autre peut-il trouver un problème à ce sujet ? Ou est-ce que cela rompt simplement avec les conventions au point de retourner l'estomac des gens ?

Voici quelques liens connexes que j'ai trouvés :

(1) Directives pour la conception d'événements [MSDN 3.5].

(2) Levée d'événement simple en C# - utilisation de l'expéditeur et des EventArgs personnalisés [StackOverflow 2009].

(3) Modèle de signature d'événement en .net [StackOverflow 2008].

Je suis intéressé par l'opinion de tous et chacun à ce sujet...

Merci d'avance,

Mike

Edit #1 : Ceci est en réponse à Le message de Tommy Carlier :

Voici un exemple fonctionnel 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 l'essayer :

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 à Déclaration d'Andrew Hare concernant la covariance et la contravariance et comment elles s'appliquent ici. Les délégués du langage C# disposent de la covariance et de la contravariance depuis si longtemps qu'ils semblent "intrinsèques", mais ils ne le sont pas. Il se peut même que ce soit quelque chose qui soit activé dans le CLR, je ne sais pas, mais Visual Basic .NET n'a pas obtenu la capacité de covariance et de contravariance pour ses délégués avant le .NET Framework 3.0 (VB.NET 2008). Par conséquent, Visual Basic.NET pour .NET 2.0 et les versions inférieures ne sont 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 l'exécuter sans problème. Mais je l'ai maintenant testé sur VB.NET 2005, juste pour être sûr, et il ne compile pas, indiquant :

Méthode 'Public Sub SomeEventHandler(sender As Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' n'a pas la même signature que delegate 'Delegate Sub StrongTypedEventHandler(Of TSender, TEventArgs As System.EventArgs)(sender As Publisher, e As PublisherEventArgs)'.

Fondamentalement, les délégués sont invariants dans VB.NET versions 2005 et inférieures. J'ai en fait pensé à cette idée il y a quelques années, mais l'incapacité de VB.NET à gérer ce problème me dérangeait... Mais je suis maintenant passé solidement à C#, et VB.NET peut maintenant le gérer, donc, eh bien, d'où ce post.

Edit : Update #3

Ok, j'utilise ce système avec succès depuis un certain temps 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;

À part ce changement de nom, j'ai mis en œuvre le système exactement comme indiqué ci-dessus.

Il passe outre la règle FxCop CA1009, qui stipule :

"Par convention, les événements .NET ont deux qui spécifient l'événement l'expéditeur et les données de l'événement. Les signatures des gestionnaires d'événements doivent suivre cette forme : void MyEventHandler( objet sender, EventArgs e). Le paramètre "sender" (expéditeur) est toujours de type System.Object, même même s'il est possible d'utiliser un type plus 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 de type délégué. Les gestionnaires d'événements renvoient void afin qu'ils puissent envoyer chaque événement à plusieurs méthodes cibles. Toute valeur retournée par une cible serait perdue après le premier appel."

Bien sûr, nous savons tout cela, et nous enfreignons quand même les règles. (Tous les gestionnaires d'événements peuvent utiliser le standard 'object Sender' dans leur signature s'ils le préfèrent dans tous les cas -- il s'agit d'un changement non cassant).

Ainsi, l'utilisation d'un 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. Elle fonctionne vraiment très bien.

Merci pour toutes vos opinions les gars, j'apprécie vraiment...

Mike

1voto

Tommy Carlier Points 3954

Et comment géreriez-vous les événements sur les interfaces ? La cible serait du type interface ?

1voto

Scott Weinstein Points 11404

Allez-y. Pour le code non basé sur des composants, je simplifie souvent les signatures d'événements pour qu'elles soient simplement

public event Action<MyEventType> EventName

MyEventType n'hérite pas de EventArgs . A quoi bon, si je n'ai jamais l'intention d'utiliser un des membres de EventArgs.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X