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<,>
. 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