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]
(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 SuppressMessageAttribute
zał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
oh hi this my hom work solve it plz :code dump:
pytań o rozmiarze tweeta , ale pytanie, z którego się uczymy .EventHandler<,>
niżGenericEventHandler<,>
. WEventHandler<>
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ówOdpowiedzi:
Wygląda na to, że Microsoft podchwycił to, ponieważ podobny przykład znajduje się teraz w MSDN:
Delegaci ogólni
źródło
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.
źródło
Środowisko wykonawcze systemu Windows (WinRT) wprowadza
TypedEventHandler<TSender, TResult>
delegata, który robi dokładnie to, coStrongTypedEventHandler<TSender, TResult>
robisz, ale najwyraźniej bez ograniczeniaTResult
parametru typu:Dokumentacja MSDN jest tutaj .
źródło
EventArgs
, to tylko kwestia konwencjiargs
, że będzie,null
jeś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 typuEventArgs
mogł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.Nie zgadzam się z następującymi stwierdzeniami:
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ł.
źródło
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:
Wydaje się, że jest to najlepszy sposób, biorąc pod uwagę użycie nazwy TypedEventHandler w WinRT.
źródło
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.
źródło
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.
źródło
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]
[Przykład 2]
źródło
W obecnej sytuacji (nadawcą jest obiekt) można łatwo dołączyć metodę do wielu zdarzeń:
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.
źródło
Nie sądzę, żeby było coś złego w tym, co chcesz zrobić. W większości podejrzewam, że
object sender
parametr 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:
Następnie możesz zadeklarować swoje wydarzenia w ten sposób
I takie metody:
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 kierunku
DataEventArgs
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
DataEventArgs
jest to, że możesz łączyć zdarzenia, zmieniając nadawcę (tak, aby reprezentował ostatniego nadawcę), podczas gdy EventArgs zachowuje pierwotnego nadawcę.źródło
Idź po to. W przypadku kodu nieskładnikowego często upraszczam podpisy zdarzeń, aby były proste
skąd
MyEventType
nie dziedziczyEventArgs
. Po co zawracać sobie głowę, jeśli nigdy nie zamierzam używać żadnego z członków EventArgs.źródło
event Action<S, T>
,event Action<R, S, T>
itd. Mam metodę rozszerzenia doRaise
nich nitki bezpiecznie :)