Podpis zdarzenia w .NET - używasz silnie wpisanego „nadawcy”?

107

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

(1) Czy powinienem rozważyć użycie tego do własnej pracy rozwojowej, która jest w 100% do celów wewnętrznych.

(2) Czy jest to koncepcja, którą projektanci frameworka mogliby rozważyć zmienić lub zaktualizować?

Myślę o użyciu sygnatury zdarzenia, która wykorzystuje silnie wpisany „nadawca”, zamiast wpisywać go jako „obiekt”, co jest bieżącym wzorcem projektowym .NET. Oznacza to, że zamiast używać standardowego podpisu zdarzenia, który wygląda następująco:

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

Rozważam użycie podpisu zdarzenia, który wykorzystuje silnie wpisany parametr „nadawca” w następujący sposób:

Najpierw zdefiniuj „StrongTypedEventHandler”:

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

To nie wszystko różni się od Action <TSender, TEventArgs>, ale wykorzystując the StrongTypedEventHandler, egzekwujemy, z którego wywodzi się TEventArgs System.EventArgs.

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

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

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

Powyższe rozwiązanie umożliwiłoby subskrybentom korzystanie z procedury obsługi zdarzeń o silnym typie, która nie wymagałaby rzutowania:

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

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 subskrybentowi użycie tradycyjnej sygnatury obsługi zdarzeń w razie potrzeby:

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

Oznacza to, że jeśli program obsługi zdarzeń musiał zasubskrybować zdarzenia z różnych (lub być może nieznanych) typów obiektów, może wpisać parametr „sender” jako „object”, aby obsłużyć pełny zakres potencjalnych obiektów nadawcy.

Poza łamaniem konwencji (co jest czymś, czego nie lekceważę, wierz mi), nie mogę wymyślić żadnych wad.

Mogą wystąpić pewne problemy ze 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. [Edycja: Od tego czasu przetestowałem to i zostało to potwierdzone: VB.NET 2005 i poniżej nie mogą sobie z tym poradzić, ale VB.NET 2008 jest w 100% w porządku. Zobacz „Edycja nr 2” poniżej.] Mogą istnieć inne języki .NET, które również mają z tym problem, nie jestem pewien.

Ale nie wydaje mi się, żebym tworzył dla żadnego 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 wyobrażałem sobie powrotu do 2.0 w tym momencie.)

Czy ktoś może pomyśleć o problemie z tym? Czy może to po prostu tak bardzo zrywa z konwencją, że powoduje to skręcenie żołądków?

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

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

(2) Proste podnoszenie zdarzeń w języku C # - używanie „nadawcy” w porównaniu z niestandardowymi EventArgs [StackOverflow 2009]

(3) Wzorzec sygnatury zdarzenia w .net [StackOverflow 2008]

Interesuje mnie opinia wszystkich i wszystkich na ten temat ...

Z góry dziękuję,

Mikrofon

Edycja nr 1: To jest odpowiedź na post Tommy'ego Carliera :

Oto pełny przykład roboczy, który pokazuje, że zarówno programy obsługi zdarzeń o silnym typie, jak i bieżące standardowe programy obsługi zdarzeń, które używają parametru „nadawca obiektu”, mogą współistnieć z tym podejściem. Możesz skopiować i wkleić kod i uruchomić go:

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

Edycja nr 2: Jest to odpowiedź na oświadczenie Andrew Hare'a dotyczące kowariancji i kontrawariancji oraz ich zastosowania tutaj. Delegaci w języku C # mają kowariancję i kontrawariancję przez tak długi czas, że wydaje się to po prostu „wewnętrzne”, ale tak nie jest. Może to być nawet coś, co jest włączone w środowisku CLR, nie wiem, ale Visual Basic .NET nie miał możliwoś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 starszych 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:

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 może go uruchomić w 100% dobrze. Ale teraz przetestowałem go na VB.NET 2005, dla pewności, i nie kompiluje się, stwierdzając:

Metoda „Public Sub SomeEventHandler (sender As Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)” nie ma tego samego podpisu co delegat „Delegate Sub StrongTypedEventHandler (Of TSender, TEventArgs As System.EventArgs As System.EventArgs As System.EventArgs) '

Zasadniczo delegaci są niezmienni w wersjach VB.NET 2005 i starszych. Właściwie pomyślałem o tym pomyśle kilka lat temu, ale niezdolność VB.NET do poradzenia sobie z tym przeszkadzała mi ... Ale teraz przeniosłem się solidnie do C # i VB.NET teraz sobie z tym poradzi, więc cóż, stąd ten post.

Edycja: aktualizacja nr 3

Ok, od jakiegoś czasu z powodzeniem używam tego. To naprawdę fajny system. Zdecydowałem się nazwać moje „StrongTypedEventHandler” jako „GenericEventHandler”, zdefiniowane w następujący sposób:

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

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

Potknie się o regułę FxCop CA1009, która stanowi:

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

Oczywiście wiemy to wszystko i tak czy inaczej łamiemy zasady. (Wszystkie programy obsługi zdarzeń mogą używać standardowego „obiektu Sender” w swoim podpisie, jeśli jest to preferowane w każdym przypadku - jest to zmiana nierozerwalna).

Więc użycie a SuppressMessageAttributezałatwia sprawę:

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

Mam nadzieję, że to podejście stanie się kiedyś standardem. Naprawdę działa bardzo ładnie.

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

Mikrofon

Mike Rosenblum
źródło
6
Zrób to. (Nie myśl, że to uzasadnia odpowiedź.)
Konrad Rudolph
1
Moje argumenty nie były skierowane przeciwko tobie: oczywiście powinieneś to robić we własnych projektach. Były argumentami, dlaczego to może nie działać w BCL.
Tommy Carlier,
3
Człowieku, chciałbym, żeby mój projekt robił to od samego początku, nienawidzę przesyłać nadawcy.
Matt H,
7
Teraz TO jest pytanie. Widzicie, ludzie? Nie jest to jedno z tych oh hi this my hom work solve it plz :code dump:pytań o rozmiarze tweeta , ale pytanie, z którego się uczymy .
Camilo Martin
3
Kolejna sugestia, po prostu nazwij ją EventHandler<,>niż GenericEventHandler<,>. W EventHandler<>BCL istnieje już rodzaj ogólny, który nazywa się po prostu EventHandler. Więc EventHandler jest bardziej popularną nazwą, a delegaci obsługują przeciążenia typów
nawfal

Odpowiedzi:

25

Wygląda na to, że Microsoft podchwycił to, ponieważ podobny przykład znajduje się teraz w MSDN:

Delegaci ogólni

Bas
źródło
2
+1 Ah, świetnie. Rzeczywiście podchwycili to. To jest dobre. Mam jednak nadzieję, że uczynią to rozpoznawalnym wzorcem w VS IDE, ponieważ tak jak jest teraz, użycie tego wzorca jest bardziej niewygodne w zakresie IntelliSense, itp.
Mike Rosenblum
13

To, co proponujesz, ma w rzeczywistości dużo sensu i po prostu zastanawiam się, czy jest to jedna z tych rzeczy, ponieważ została pierwotnie zaprojektowana przed lekami generycznymi, czy też istnieje ku temu prawdziwy powód.

BFree
źródło
1
Jestem pewien, że to jest właśnie powód. Jednak teraz, gdy nowsze wersje języka mają sprzeczność, aby sobie z tym poradzić, wygląda na to, że powinny być w stanie obsłużyć to w sposób zgodny z poprzednimi wersjami. Poprzednie programy obsługujące, które używają „obiektu nadawcy”, nie uległy awarii. Ale nie jest to prawdą w przypadku starszych języków i może nie być prawdą w przypadku niektórych obecnych języków .NET, nie jestem pewien.
Mike Rosenblum
13

Środowisko wykonawcze systemu Windows (WinRT) wprowadza TypedEventHandler<TSender, TResult>delegata, który robi dokładnie to, co StrongTypedEventHandler<TSender, TResult>robisz, ale najwyraźniej bez ograniczenia TResultparametru typu:

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

Dokumentacja MSDN jest tutaj .

Pierre Arnaud
źródło
1
Ach, dobrze widzieć, że jest postęp ... Zastanawiam się, dlaczego TResult nie ogranicza się do dziedziczenia z klasy „EventArgs”. Klasa bazowa „EventArgs” jest zasadniczo pusta; może odchodzą od tego ograniczenia?
Mike Rosenblum
Może to być przeoczenie ze strony zespołu projektowego; kto wie.
Pierre Arnaud
cóż, wydarzenia działają dobrze bez użycia EventArgs, to tylko kwestia konwencji
Sebastian
3
W szczególności w dokumentacji TypedEventHandler stwierdza args, że będzie, nulljeśli nie ma danych o zdarzeniach, więc wydaje się, że domyślnie unikają używania zasadniczo pustego obiektu. Domyślam się, że pierwotny pomysł polegał na tym, że metoda z drugim parametrem typu EventArgsmogłaby obsłużyć każde zdarzenie, ponieważ typy byłyby zawsze zgodne. Prawdopodobnie zdają sobie teraz sprawę, że możliwość obsługi wielu różnych zdarzeń za pomocą jednej metody nie jest aż tak ważna.
jmcilhinney,
1
To nie wygląda na przeoczenie. Ograniczenie zostało również usunięte z delegata System.EventHandler <TEventArgs>. referenceource.microsoft.com/#mscorlib/system/…
colton7909
5

Nie zgadzam się z następującymi stwierdzeniami:

  • Uważam, że starsze wersje Visual Basic .NET do 2005 roku nie mają kowariancji i kontrawariancji delegatów.
  • W pełni zdaję sobie sprawę, że to graniczy z bluźnierstwem.

Po pierwsze, nic, co tu zrobiłeś, nie ma nic wspólnego z kowariancją lub kontrawariancją. ( Edycja: poprzednie stwierdzenie jest błędne, aby uzyskać więcej informacji, zobacz temat Kowariancja i kontrawariancja w delegatach ) To rozwiązanie będzie działać dobrze we wszystkich wersjach CLR 2.0 i nowszych (oczywiście nie będzie działać w aplikacji CLR 1.0, ponieważ używa typów ogólnych ).

Po drugie, zdecydowanie nie zgadzam się, że twój pomysł graniczy z „bluźnierstwem”, ponieważ jest to wspaniały pomysł.

Andrew Hare
źródło
2
Cześć Andrew, dzięki za kciuki do góry! Biorąc pod uwagę Twój poziom reputacji, to naprawdę wiele dla mnie znaczy ... W kwestii kowariancji / kontrawariancji: jeśli delegat dostarczony przez subskrybenta nie pasuje dokładnie do podpisu wydarzenia wydawcy, to w grę wchodzą kowariancja i kontrawariancja. C # ma delegowaną kowariancję i kontrawariancję od zawsze, więc wydaje się to nieodłączne, ale VB.NET nie miał delegowanej kowariancji i kontrawariancji aż do .NET 3.0. Dlatego VB.NET dla .NET 2.0 i starszych nie będzie mógł korzystać z tego systemu. (Zobacz przykład kodu, który dodałem w sekcji „Edycja nr 2” powyżej).
Mike Rosenblum,
@Mike - Przepraszam, masz 100% racji! Zredagowałem moją odpowiedź, aby odzwierciedlić twój punkt widzenia :)
Andrew Hare
4
Ach, ciekawe! Wygląda na to, że kowariancja / kontrawariancja delegata jest częścią CLR, ale (z powodów, których nie znam) nie została ujawniona przez VB.NET przed najnowszą wersją. Oto artykuł Francesco Baleny, który pokazuje, jak można osiągnąć wariancję delegatów za pomocą odbicia, jeśli nie jest włączona przez sam język: dotnet2themax.com/blogs/fbalena/… .
Mike Rosenblum
1
@Mike - zawsze interesujące jest nauczenie się rzeczy, które CLR obsługuje, ale nie są obsługiwane w żadnym z języków .NET.
Andrew Hare
5

Rzuciłem okiem, jak sobie z tym radzono w nowym WinRT i na podstawie innych opinii tutaj, i ostatecznie zdecydowałem się zrobić to w ten sposób:

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

Wydaje się, że jest to najlepszy sposób, biorąc pod uwagę użycie nazwy TypedEventHandler w WinRT.

Inverness
źródło
Po co dodawać ogólne ograniczenie w TEventArgs? Został usunięty z EventHandler <> i TypedEventHandler <,>, ponieważ tak naprawdę nie miał sensu.
Mike Marynowski
2

Myślę, że to świetny pomysł, a MS może po prostu nie mieć czasu lub zainteresowania, aby zainwestować w ulepszenie tego, na przykład kiedy przeniosło się z ArrayList na listy oparte na rodzajach.

Otávio Décio
źródło
Możesz mieć rację ... Z drugiej strony, myślę, że to tylko „standard”, a być może wcale nie jest to kwestia techniczna. To znaczy, ta możliwość może być obecna we wszystkich obecnych językach .NET, nie wiem. Wiem, że C # i VB.NET sobie z tym poradzą. Jednak nie jestem pewien, jak szeroko działa to we wszystkich obecnych językach .NET ... Ale ponieważ działa w C # i VB.NET, a wszyscy tutaj są tak pomocni, myślę, że prawdopodobnie to zrobię. :-)
Mike Rosenblum
2

Z tego co rozumiem, pole „Nadawca” ma zawsze odnosić się do obiektu, który przechowuje subskrypcję zdarzenia. Gdybym mógł sobie pozwolić, byłoby również pole przechowujące informacje wystarczające do anulowania subskrypcji zdarzenia, gdyby stało się to konieczne (*) (rozważ na przykład rejestrator zmian, który subskrybuje zdarzenia `` zmienione w kolekcji ''; zawiera dwie części , z których jeden wykonuje rzeczywistą pracę i przechowuje rzeczywiste dane, a drugi zapewnia otokę interfejsu publicznego, główna część może zawierać słabe odwołanie do części opakowania. Jeśli część opakowania zostanie zebrana jako śmieci, oznaczałoby to nie było już nikogo, kto byłby zainteresowany zbieranymi danymi, zatem rejestrator zmian powinien wypisać się z otrzymywanych zdarzeń).

Ponieważ jest możliwe, że obiekt może wysyłać zdarzenia w imieniu innego obiektu, widzę potencjalną użyteczność posiadania pola „nadawca”, które jest typu Object, oraz posiadania pola pochodnego EventArgs, które zawiera odniesienie do obiektu, które powinno podlegać działaniu. Użyteczność pola „nadawca” jest jednak prawdopodobnie ograniczona przez fakt, że nie ma jasnego sposobu na wypisanie się obiektu od nieznanego nadawcy.

(*) Właściwie czystszym sposobem obsługi anulowania subskrypcji byłoby posiadanie typu delegata multiemisji dla funkcji, które zwracają wartość logiczną; jeśli funkcja wywołana przez takiego delegata zwróci True, delegat zostanie poprawiony, aby usunąć ten obiekt. Oznaczałoby to, że delegaci nie byliby już naprawdę niezmienni, ale powinno być możliwe dokonanie takiej zmiany w sposób bezpieczny dla wątków (np. Przez zerowanie odwołania do obiektu i zignorowanie przez kod delegata multiemisji wszelkich osadzonych odwołań do obiektów o wartości null). W tym scenariuszu próba opublikowania i zdarzenia do usuniętego obiektu mogłaby zostać obsłużona bardzo czysto, bez względu na to, skąd pochodzi zdarzenie.

supercat
źródło
2

Patrząc wstecz na bluźnierstwo jako jedyny powód, dla którego nadawca stał się typem obiektu (jeśli pominąć problemy z kontrawariancją w kodzie VB 2005, co jest błędem Microsoftu IMHO), może ktoś zasugerować przynajmniej teoretyczny motyw przybijania drugiego argumentu do typu EventArgs. Idąc nawet dalej, czy istnieje dobry powód, aby zastosować się do wytycznych i konwencji Microsoftu w tym konkretnym przypadku?

Konieczność opracowania kolejnego opakowania EventArgs dla innych danych, które chcemy przekazać wewnątrz modułu obsługi zdarzeń, wydaje się dziwne, dlaczego nie można bezpośrednio przekazać tam tych danych. Rozważ następujące sekcje kodu

[Przykład 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;
}

[Przykład 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
źródło
2
Tak, utworzenie oddzielnej klasy dziedziczącej po System.EventArgs może wydawać się nieintuicyjne i wymaga dodatkowej pracy, ale jest ku temu bardzo dobry powód. Jeśli nigdy nie musisz zmieniać kodu, Twoje podejście jest w porządku. Ale w rzeczywistości może być konieczne zwiększenie funkcjonalności zdarzenia w przyszłej wersji i dodanie właściwości do argumentów zdarzenia. W twoim scenariuszu należałoby dodać dodatkowe przeciążenia lub opcjonalne parametry do podpisu programu obsługi zdarzeń. Jest to podejście stosowane w VBA i starszej wersji VB 6.0, które jest wykonalne, ale w praktyce trochę brzydkie.
Mike Rosenblum
1
Jednak dziedzicząc po EventArgs, przyszła wersja może dziedziczyć ze starszej klasy argumentów zdarzenia i ją rozszerzać. Wszystkie starsze wywołujące mogą nadal działać dokładnie tak, jak jest, działając w oparciu o klasę bazową nowej klasy argumentów zdarzeń. Bardzo czysto. Więcej pracy dla Ciebie, ale czystsze dla wszystkich dzwoniących zależnych od Twojej biblioteki.
Mike Rosenblum
Nie musi nawet dziedziczyć, możesz po prostu dodać dodatkową funkcjonalność bezpośrednio do swojej klasy argumentów zdarzenia i będzie nadal działać poprawnie. To powiedziawszy, ograniczenie przypinające argumenty do argumentów zdarzeń zostało usunięte, ponieważ nie miało większego sensu w wielu scenariuszach, tj. gdy wiesz, że nigdy nie będziesz musiał rozszerzać funkcjonalności określonego zdarzenia lub gdy wszystko, czego potrzebujesz, to argument typu wartości w aplikacjach bardzo wrażliwych na wydajność.
Mike Marynowski
1

W obecnej sytuacji (nadawcą jest obiekt) można łatwo dołączyć metodę do wielu zdarzeń:

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

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

Jeśli nadawca byłby ogólny, cel zdarzenia kliknięcia nie byłby typu Button lub Label, ale typu Control (ponieważ zdarzenie jest zdefiniowane w Control). Więc niektóre zdarzenia w klasie Button miałyby cel typu Control, inne miałyby inne typy docelowe.

Tommy Carlier
źródło
2
Tommy, możesz zrobić dokładnie to samo z systemem, który proponuję. Nadal można używać standardowej procedury obsługi zdarzeń, która ma parametr „nadawca obiektu” do obsługi tych zdarzeń o silnym typie. (Zobacz przykład kodu, który dodałem teraz do oryginalnego postu.)
Mike Rosenblum
Tak, zgadzam się, to jest dobra rzecz w przypadku standardowych wydarzeń .NET, zaakceptowana!
Lu4
1

Nie sądzę, żeby było coś złego w tym, co chcesz zrobić. W większości podejrzewam, że object senderparametr pozostaje, aby nadal obsługiwać kod pre 2.0.

Jeśli naprawdę chcesz wprowadzić tę zmianę dla publicznego interfejsu API, możesz rozważyć utworzenie własnej podstawowej klasy EvenArgs. Coś takiego:

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; } }
}

Następnie możesz zadeklarować swoje wydarzenia w ten sposób

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

I takie metody:

private void HandleSomething(object sender, EventArgs e)

nadal będzie mógł subskrybować.

EDYTOWAĆ

Ta ostatnia linijka dała mi trochę do myślenia ... Właściwie powinieneś być w stanie zaimplementować to, co proponujesz, bez przerywania jakichkolwiek zewnętrznych funkcji, ponieważ środowisko wykonawcze nie ma problemu z obniżaniem parametrów. Nadal pochylałbym się w kierunkuDataEventArgs rozwiązaniu (osobiście). Zrobiłbym to, wiedząc jednak, że jest to zbędne, ponieważ nadawca jest przechowywany w pierwszym parametrze i jako właściwość zdarzenia args.

Jedną z korzyści wynikających z trzymania się tego DataEventArgsjest to, że możesz łączyć zdarzenia, zmieniając nadawcę (tak, aby reprezentował ostatniego nadawcę), podczas gdy EventArgs zachowuje pierwotnego nadawcę.

Michael Meadows
źródło
Hej Michael, to całkiem fajna alternatywa. Lubię to. Jak jednak wspomniałeś, dwukrotne skuteczne przekazywanie parametru „nadawca” jest zbędne. Podobne podejście jest omówione tutaj: stackoverflow.com/questions/809609/… i wydaje się, że jest to zbyt niestandardowe. Dlatego wahałem się, czy zasugerować tutaj silnie wpisaną ideę „nadawcy”. (Wydaje się jednak, że został dobrze przyjęty, więc jestem zadowolony.)
Mike Rosenblum,
1

Idź po to. W przypadku kodu nieskładnikowego często upraszczam podpisy zdarzeń, aby były proste

public event Action<MyEventType> EventName

skąd MyEventTypenie dziedziczy EventArgs. Po co zawracać sobie głowę, jeśli nigdy nie zamierzam używać żadnego z członków EventArgs.

Scott Weinstein
źródło
1
Zgodzić się! Dlaczego powinniśmy czuć się małpami?
Lu4
1
+ 1-ed, ja też tego używam. Czasami wygrywa prostota! Albo nawet event Action<S, T>, event Action<R, S, T>itd. Mam metodę rozszerzenia do Raisenich nitki bezpiecznie :)
Nawfal