Podpis zdarzenia w .NET - korzystanie z silnego wpisanego „nadawcy”?

W pełni zdaję sobie sprawę z tego, że to, co proponuję, nie jest zgodne z wytycznymi .NET i dlatego z tego powodu prawdopodobnie jest kiepskim pomysłem. Chciałbym jednak rozważyć to z dwóch możliwych perspektyw:

(1) Czy powinienem rozważyć użycie tego do własnych prac rozwojowych, które są w 100% przeznaczone do celów wewnętrznych.

(2) Czy jest to koncepcja, którą projektanci ram mogliby zmienić lub zaktualizować?

Zastanawiam się nad użyciem sygnatury zdarzenia, która używa silnego „nadawcy”, zamiast wpisywać go jako „obiekt”, który jest aktualnym wzorcem projektu .NET. Oznacza to, że zamiast używać standardowej sygnatury zdarzenia, która wygląda tak:

<code>class Publisher
{
    public event EventHandler<PublisherEventArgs> SomeEvent;
}
</code>

Rozważam użycie sygnatury zdarzenia, która wykorzystuje silny typ „nadawcy”, jak następuje:

Najpierw zdefiniuj „StrongTypedEventHandler”:

<code>[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
    TSender sender,
    TEventArgs e
)
where TEventArgs : EventArgs;
</code>

Nie różni się to niczym od akcji <TSender, TEventArgs>, ale z wykorzystaniemStrongTypedEventHandler, wymuszamy, że TEventArgs pochodziSystem.EventArgs.

Następnie, jako przykład, możemy użyć StrongTypedEventHandler w klasie publikacyjnej w następujący sposób:

<code>class Publisher
{
    public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;

    protected void OnSomeEvent()
    {
        if (SomeEvent != null)
        {
            SomeEvent(this, new PublisherEventArgs(...));
        }
    }
}
</code>

Powyższe rozwiązanie umożliwiłoby abonentom korzystanie z obsługi zdarzeń o silnym typie, która nie wymagała odlewania:

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

W pełni zdaję sobie sprawę, że to zrywa ze standardowym wzorcem obsługi zdarzeń .NET; należy jednak pamiętać, że kontrawariancja umożliwiłaby abonentowi użycie w razie potrzeby tradycyjnego podpisu obsługi zdarzeń:

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

Oznacza to, że jeśli moduł obsługi zdarzeń musiałby subskrybować zdarzenia z różnych (lub może nieznanych) typów obiektów, program obsługi mógłby wpisać parametr „nadawca” jako „obiekt”, aby obsłużyć pełną szerokość potencjalnych obiektów nadawcy.

Poza łamaniem konwencji (co jest czymś, czego nie biorę lekko, wierzcie mi) nie mogę myśleć o żadnych wadach.

Tutaj mogą występować pewne problemy z zgodnością z CLS. Działa to w Visual Basic .NET 2008 w 100% w porządku (testowałem), ale uważam, że starsze wersje Visual Basic .NET do 2005 roku nie mają kowariancji i kontrawariancji delegatów.[Edytuj: Od tego czasu przetestowałem to i jest potwierdzone: VB.NET 2005 i poniżej nie mogą tego znieść, ale VB.NET 2008 jest w 100% w porządku. Zobacz „Edytuj # 2” poniżej.] Mogą istnieć inne języki .NET, które również mają z tym problem, nie mogę być tego pewien.

Ale nie widzę, żebym rozwijał się dla jakiegokolwiek języka innego niż C # lub Visual Basic .NET i nie mam nic przeciwko ograniczeniu go do C # i VB.NET dla .NET Framework 3.0 i nowszych. (Szczerze mówiąc, nie mogłem sobie wyobrazić powrotu do 2.0).

Czy ktoś inny może pomyśleć o tym problemie? A może po prostu zrywa z konwencją tak bardzo, że sprawia, że ​​żołądki ludzi się obracają?

Oto kilka powiązanych linków, które znalazłem:

(1)Wytyczne dotyczące projektowania zdarzeń [MSDN 3.5]

(2)C # simple Event Raising - za pomocą „sender” a niestandardowe EventArgs [StackOverflow 2009]

(3)Wzór sygnatur zdarzeń w .net [StackOverflow 2008]

Jestem zainteresowany opinią kogokolwiek i wszystkich na ten temat ...

Z góry dziękuję,

Mikrofon

Edytuj # 1: To jest odpowiedź naTommy Carlier's post:

Oto pełny przykład działania, który pokazuje, że zarówno silne procedury obsługi zdarzeń, jak i bieżące standardowe procedury obsługi zdarzeń, które wykorzystują parametr „nadawca obiektów”, mogą współistnieć z tym podejściem. Możesz skopiować i wkleić kod i uruchomić go:

<code>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();
        }
    }
}
</code>

Edytuj # 2: To jest odpowiedź naOświadczenie Andrew Hare'a w odniesieniu do kowariancji i kontrawariancji oraz tego, jak ma to zastosowanie tutaj. Delegaci w języku C # mieli kowariancję i kontrawariancję tak długo, że czują się „wewnętrznie”, ale tak nie jest. To może być nawet coś, co jest włączone w CLR, nie wiem, ale Visual Basic .NET nie otrzymał zdolności kowariancji i kontrawariancji dla swoich delegatów aż do .NET Framework 3.0 (VB.NET 2008). W rezultacie Visual Basic.NET dla .NET 2.0 i niższych nie będzie w stanie wykorzystać tego podejścia.

Na przykład powyższy przykład można przetłumaczyć na VB.NET w następujący sposób:

<code>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
</code>

VB.NET 2008 może uruchomić go w 100% w porządku. Ale teraz przetestowałem go na VB.NET 2005, żeby się upewnić, i nie kompiluje się, stwierdzając:

Metoda 'Publiczny podrzędny SomeEventHandler (nadawca jako obiekt, e jako vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' nie ma tego samego podpisu co delegat 'Delegat Sub StrongTypedEventHandler (Of TSender, TEventArgs As System.EventArgs) (nadawca jako wydawca, e jako PublisherEventArgs) '

Zasadniczo delegaci są niezmienni w wersjach VB.NET 2005 i niższych. Pomyślałem o tym pomyśle kilka lat temu, ale niezdolność VB.NET do radzenia sobie z tym problemem mnie niepokoiła ... Ale teraz przeniosłem się solidnie do C #, a VB.NET może teraz sobie z tym poradzić, więc, cóż, stąd ten post.

Edytuj: Aktualizacja # 3

Ok, od jakiegoś czasu korzystam z tego całkiem pomyślnie. To naprawdę fajny system. Postanowiłem nazwać „StrongTypedEventHandler” jako „GenericEventHandler”, zdefiniowany w następujący sposób:

<code>[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
    TSender sender,
    TEventArgs e
)
where TEventArgs : EventArgs;
</code>

Oprócz zmiany nazwy zaimplementowałem ją dokładnie tak, jak omówiono powyżej.

Potyka się o regułę FxCop CA1009, która stwierdza:

„Zgodnie z konwencją zdarzenia .NET mają dwa parametry, które określają nadawcę zdarzenia i dane zdarzenia. Podpisy obsługi zdarzeń powinny być następujące: void MyEventHandler (nadawca obiektu, EventArgs e). Parametr„ nadawca ”jest zawsze typu System.Object, nawet jeśli możliwe jest zastosowanie bardziej szczegółowego typu. Parametr „e” ma zawsze typ System.EventArgs. Zdarzenia, które nie dostarczają danych o zdarzeniach, powinny używać typu delegowania System.EventHandler. Procedury obsługi zdarzeń zwracają void, aby mogły wysyłać każde zdarzenie do wielu metod docelowych Każda wartość zwracana przez cel zostałaby utracona po pierwszym wywołaniu. ”

Oczywiście wiemy to wszystko i i tak łamiemy zasady. (Wszystkie moduły obsługi zdarzeń mogą używać standardowego „obiektu Nadawcy” w swoim podpisie, jeśli jest to preferowane w każdym przypadku - jest to zmiana bez złamania).

Więc użycie aSuppressMessageAttribute Zrób sztuczkę:

<code>[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
    Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
</code>

Mam nadzieję, że takie podejście stanie się standardem w pewnym momencie w przyszłości. To naprawdę działa bardzo ładnie.

Dzięki za wszystkie twoje opinie, naprawdę to doceniam ...

Mikrofon

questionAnswers(11)

yourAnswerToTheQuestion