2012-03-31 9 views
5

Próbowałem utworzyć ogólne wydarzenie. Zasadniczo powinno to wyglądać tak:EventHandlers i Covariance

namespace DelegateTest 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var lol = new SomeClass(); 
      lol.SomeEvent += handler; 
     } 

     static void handler(object sender, SomeDerivedClass e) 
     { 

     } 

    } 

    class SomeClass 
    { 

     public delegate void SomeEventDelegate<in T>(object sender, T data); 
     public event SomeEventDelegate<ISomeInterface> SomeEvent; 

    } 

    interface ISomeInterface 
    { 
    } 

    class SomeDerivedClass : ISomeInterface 
    { 
    } 
} 

Chcę, aby umożliwić użytkownikowi przekazać wszelkie delegata, który jest drugim parametrem jest pochodną „ISomeInterface”.

"in" określa contra-wariancję, prawda? Oznacza to, że jeśli API spodziewa się czegoś bardziej ogólnego, możesz przekazać mu coś bardziej konkretnego (w mojej bazie "ISomeInterface" będzie ogólne, a moja "SomeDerivedClass" będzie specyficzna.) Jednak zostałem poinformowany o moim kompilatorze że "żadne przeciążenie dla metody obsługi nie pasuje do DelegateTest.SomeClass.SomeEventDelegate."

Zastanawiam się, dlaczego to nie działa. Jakie są problemy, które mogłyby powstać, gdyby tak było? Czy może brakuje mi czegoś, żeby zadziałało?

Z góry dziękuję!

+0

Zobacz http://stackoverflow.com/questions/129453/net-eventhandlers-generic-or-no –

+0

Myślę, że nie można mieć jednego zdarzenia do obsługi wielu typów, można mieć tylko delegatów. Możesz zmienić program obsługi, aby zaakceptował 'ISomeInterface', a następnie sprawdź typ obiektu, który wysłał. Lub użyj samego interfejsu. http://msdn.microsoft.com/en-us/library/dd469484.aspx – BrunoLM

Odpowiedz

6

"in" określa contra-wariancję, prawda?

Tak.

Oznacza to, że jeśli API jest spodziewałem się czegoś bardziej ogólnego, można przekazać to coś bardziej szczegółowe (w mojej bazy „ISomeInterface” będzie ogólnie, a mój „SomeDerivedClass” byłoby specyficzne).

Nie. Delegowanie kontrawariancji umożliwia delegatowi odwoływanie się do metody z typami parametrów, które są mniej pochodne niż w typie delegata. Na przykład, załóżmy ISomeInterface miał interfejs bazowy:

interface ISomeBaseInterface 
{ 
} 

interface ISomeInterface : ISomeBaseInterface 
{ 
} 

I przypuśćmy handler wziął ISomeBaseInterface zamiast SomeDerivedClass:

static void handler(object sender, ISomeBaseInterface e) 

Następnie new SomeClass().SomeEvent += handler będzie działać.

Oto dlaczego oryginalny kod nie jest bezpieczny typ: Kiedy SomeClass podnosi SomeEvent, może potencjalnie przekazać nic który implementuje ISomeInterface jako data argument. Na przykład, może to przejść instancję SomeDerivedClass, ale może również przekazać instancję

class SomeOtherDerivedClass : ISomeInterface 
{ 
} 

Jeśli były w stanie zarejestrować void handler(object sender, SomeDerivedClass e) ze zdarzeniem, które teleskopowa by skończyć powołano z SomeOtherDerivedClass, który nie robi” t działa.

Podsumowując, można zarejestrować obsługi zdarzeń, które są bardziej ogólnego od typu zdarzenia, a nie obsługi zdarzeń, które są bardziej szczegółowe.

UPDATE: Skomentowales:

Cóż, tak naprawdę chcą iterację listy i sprawdzić typy.Jeśli więc zdarzenie miało zostać uruchomione przy użyciu obiektu danych typu, powiedzmy SomeOtherDerivedObject, program będzie iterował listę metod subskrybowanych do zdarzenia, dopóki nie znajdzie takiego, który pasuje do podpisu (obiekt SomeOtherDerivedObject). Więc samo wydarzenie będzie używane tylko do przechowywania, a nie do wywoływania delegatów.

Nie sądzę, C# pozwala zadeklarować event, który działa z arbitralnych typów delegatów. Oto w jaki sposób można napisać metod, które dodają obsługi zdarzeń i wywoływać je:

class SomeClass 
{ 
    private Delegate handlers; 

    public delegate void SomeEventDelegate<in T>(object sender, T data); 

    public void AddSomeEventHandler<T>(SomeEventDelegate<T> handler) 
    { 
     this.handlers = Delegate.Combine(this.handlers, handler); 
    } 

    protected void OnSomeEvent<T>(T data) 
    { 
     if (this.handlers != null) 
     { 
      foreach (SomeEventDelegate<T> handler in 
       this.handlers.GetInvocationList().OfType<SomeEventDelegate<T>>()) 
      { 
       handler(this, data); 
      } 
     } 
    } 
} 
+0

Dzięki. Czy jest jakiś sposób, aby sprawić, by działało to na odwrót? Obecnie używam podejścia podobnego do Java (interfejsy + addListener). – haiyyu

+1

Jeśli jesteś absolutnie pewien, że 'SomeClass' z kiedykolwiek przekazuje instancje' SomeDerivedClass' jako argumentem 'data', możesz dodać jawną obsadę : 'lol.SomeEvent + = (nadawca, e) => handler (nadawca, (SomeDerivedClass) e)'. Ale równie dobrze możesz zadeklarować 'SomeEvent' jako' SomeEventDelegate '. –

+0

Cóż, rzeczywiście chcę iterować listę i sprawdzać typy. Jeśli więc zdarzenie miało zostać uruchomione przy użyciu obiektu danych typu, powiedzmy SomeOtherDerivedObject, program będzie iterował listę metod subskrybowanych do zdarzenia, dopóki nie znajdzie takiego, który pasuje do podpisu (obiekt SomeOtherDerivedObject). Więc samo wydarzenie będzie używane tylko do przechowywania, a nie do wywoływania delegatów. Mam nadzieję, że było to zrozumiałe. – haiyyu

2

Jednym z głównych kłopot z delegata kontrawariancji jest to, że podczas gdy delegat typu np Action<Fruit> może zostać przekazany do procedury oczekującej na Action<Banana>, próba połączenia dwóch delegatów, których rzeczywiste typy to Action<Fruit>, a Action<Banana> nie powiedzie się *, nawet jeśli obaj delegaci mają typ "kompilacji" Action<Banana>. Aby obejść ten problem, chciałbym zaproponować stosując sposób podobny do poniższego:

static T As<T>(this Delegate del) where T : class 
    { 
     if (del == null || del.GetType() == typeof(T)) return (T)(Object)del; 
     Delegate[] invList = ((Delegate)(Object)del).GetInvocationList(); 
     for (int i = 0; i < invList.Length; i++) 
      if (invList[i].GetType() != typeof(T)) 
       invList[i] = Delegate.CreateDelegate(typeof(T), invList[i].Target, invList[i].Method); 
     return (T)(Object)Delegate.Combine(invList); 
    } 

Given delegata i typ delegata, metoda ta sprawdza, czy typ przeszedł w delegata dokładnie pasuje do określonego typu; jeśli nie, ale metody (y) w oryginalnym delegacie mają odpowiednie podpisy dla określonego typu, nowy delegat zostanie utworzony z niezbędnymi cechami. Zauważ, że jeśli przy dwóch różnych okazjach przekazywana jest ta funkcja, delegaci, którzy nie są odpowiedniego typu, ale których porównywanie są sobie równi, delegaci zwróceni przez tę metodę również będą porównywać siebie nawzajem. Zatem jeśli ma się zdarzenie, które ma przyjąć delegata typu Action<string>, można zastosować powyższą metodę do konwersji np. przekazany Action<object> do "rzeczywistego" Action<string> przed dodaniem lub usunięciem ze zdarzenia.

Jeśli jeden będzie dodanie lub odjęcie przekazywana w pełnomocnik z pola właściwego typu delegatm rodzaju wnioskowanie zachowanie IntelliSense można poprawić, jeśli korzysta się z następujących metod:

static void AppendTo<T>(this Delegate newDel, ref T baseDel) where T : class 
    { 
     newDel = (Delegate)(Object)newDel.As<T>(); 
     T oldBaseDel, newBaseDel; 
     do 
     { 
      oldBaseDel = baseDel; 
      newBaseDel = (T)(Object)Delegate.Combine((Delegate)(object)oldBaseDel, newDel); 
     } while (System.Threading.Interlocked.CompareExchange(ref baseDel, newBaseDel, oldBaseDel) != oldBaseDel); 
    } 

    static void SubtractFrom<T>(this Delegate newDel, ref T baseDel) where T : class 
    { 
     newDel = (Delegate)(Object)newDel.As<T>(); 
     T oldBaseDel, newBaseDel; 
     do 
     { 
      oldBaseDel = baseDel; 
      newBaseDel = (T)(Object)Delegate.Remove((Delegate)(object)oldBaseDel, newDel); 
     } while (System.Threading.Interlocked.CompareExchange(ref baseDel, newBaseDel, oldBaseDel) != oldBaseDel); 
    } 

Metody te pojawiają się jako metody rozszerzenia dla typów pochodnych od Delegate i umożliwiają dodawanie instancji takich typów lub odejmowanie od zmiennych lub pól odpowiednich typów delegatów; takie dodawanie lub odejmowanie będzie wykonywane w sposób bezpieczny dla wątków, więc może być możliwe użycie tych metod w metodach dodawania/usuwania zdarzeń bez dodatkowego blokowania.

+0

Bardzo przydatne, dziękuję! –

Powiązane problemy