2012-05-04 15 views
7

mam delegata z typu rodzajowego jako jeden z parametrów:C# Generic Events

public delegate void UpdatedPropertyDelegate<T>(
    RemoteClient callingClient, 
    ReplicableProperty<T> updatedProp, 
    ReplicableObject relevantObject 
); 

Teraz chcę wydarzenie publiczne, które można subskrybować dla innych klas do użytku. Dlatego zrobiłem:

public event UpdatedPropertyDelegate<T> UpdatedProperty; 

Jednak kompilator tego nie lubi. Nie rozumiem, dlaczego T musi tu być określony. Z pewnością jest on określony podczas wywoływania zdarzenia, tj .:

if (UpdatedProperty != null) 
{ 
    UpdatedProperty(this, readProperty, 
     ReplicableObjectBin.GetObjectByID(readProperty.OwnerID)); 
} 

Czy robię coś prostego? Czy jest to ogromna porażka zrozumienia?

Dzięki.

+3

Kompilator nie zaakceptuje właściwości o nieznanym typie ogólnym, która nie może być znana do czasu wykonania. – BoltClock

+0

Czego można się spodziewać? – SLaks

+0

Chcę przekazać ogólny parametr w zdarzeniu. To wszystko. Na pewno, gdybym wiedział, jaki typ T byłby w czasie kompilacji, nie musiałbym w ogóle używać generycznych? – Xenoprimate

Odpowiedz

7

Brzmi to co trzeba jest typ interfejsu, a nie delegata. Metody interfejsu akceptują otwarte typy ogólne (to jest to, czego szukasz), mimo że delegaci nie mogą. Na przykład, można określić coś takiego:

interface ActOnConstrainedThing<CT1,CT2> 
{ 
    void Act<MainType>(MainType param) where MainType: CT1,CT2; 
} 

Nawet jeśli realizatorów CT1 i CT2 nie mają wspólny typ bazowy, który również implementuje CT1 i CT2, implementacja Act może używać przekazany w parametr jako CT1 lub CT2 bez typowania, a nawet może przekazać go do procedur, które oczekują ogólnego parametru z ograniczeniami CT1 i CT2. Taka rzecz nie byłaby możliwa z delegatami.

Należy zauważyć, że używanie interfejsów zamiast delegatów oznacza, że ​​nie można używać normalnego mechanizmu i składni "zdarzenia". Zamiast tego obiekt, który byłby wydawcy zdarzenia, musi utrzymywać listę instancji obiektów, które implementują pożądany interfejs (np. List<ActOnConstrainedThing<IThis,IThat>>) i wyliczyć wystąpienia na tej liście (być może używając foreeach). Na przykład:

 
List<IActOnConstrainedThing<IThis,IThat>> _ActOnThingSubscribers; 

void ActOnThings<T>(T param) where T:IThis,IThat 
{ 
    foreach(var thing in _ActOnThingSubscribers) 
    { 
    thing.Act<T>(param); 
    } 
} 

Edycja/Uzupełnienie

miejsce gdzie zatrudniony ten wzór miał także kilka innych rzeczy, które nie wydają się zbyt istotne pytanie, które przez moją interpretacją pytał, jak jeden może mieć delegata (lub odpowiednik) z parametrem typu otwartego, dzięki czemu obiekt wywołujący odpowiednik delegata może dostarczyć parametr typu, bez potrzeby dostarczania obiektu podmiotu delegującego, który musi go znać wcześniej. W większości przypadków, gdzie jest to przydatne obejmować ogólne ograniczenia, ale od tego najwyraźniej wprowadzającej w błąd, oto przykład, że nie:

 
interface IShuffleFiveThings 
{ 
    void Shuffle<T>(ref T p1, ref T p2, ref T p3, ref T p4, ref T p5); 
} 
List<IShuffleFiveThings _ShuffleSubscribers; 

void ApplyShuffles<T>(ref T p1, ref T p2, ref T p3, ref T p4, ref T p5) 
{ 
    foreach(var shuffler in _ShuffleSubscribers) 
    { 
    thing.Shuffle(ref p1, ref p2, ref p3, ref p4, ref p5); 
    } 
} 

Sposób IShuffleFiveThings.Shuffle<T> trwa pięć parametrów przez ref i robi coś z nich (najprawdopodobniej permutacji w jakiś sposób, być może permutując je losowo, lub może permutując niektóre losowo, pozostawiając innych tam, gdzie są.Jeśli ma się listę IShuffleFiveThings, rzeczy na tej liście mogą być wykorzystywane efektywnie, bez boksowania lub Odbicia, do manipulowania wszelkiego rodzaju rzeczami (w tym zarówno typami klas, jak i typami wartości). Natomiast, jeśli jeden z nich korzystać delegatów:

 
delegate void ActOn5RefParameters(ref p1, ref p2, ref p3, ref p4, ref p5); 

potem bo jakaś konkretna instancja delegat może działać tylko na jednym rodzaju parametrów dostarczanego na jego powstania (o ile nie jest to otwarty delegata, który nazywa się tylko za pośrednictwem refleksji), jeden musiałby utworzyć osobną listę delegatów dla każdego typu obiektu, który chciałby przetasować (tak, wiem, że normalnie obsłużyłby permutacje za pomocą tablicy indeksów całkowitych, wybrałem permutację jako operację, ponieważ dotyczy ona wszystkich obiektów typy, nie dlatego, że ta konkretna metoda permutacji rzeczy jest przydatna).

Należy pamiętać, że ponieważ typ T w IShuffleFiveThings nie ma żadnych ograniczeń, implementacje nie będą w stanie z nim wiele zrobić, z wyjątkiem operacji typowania (która może wprowadzić boksowanie). Dodanie ograniczeń do takich parametrów czyni je znacznie bardziej użytecznymi. Chociaż możliwe byłoby sztywne kodowanie takich ograniczeń w interfejsie, ograniczyłoby to przydatność interfejsu dla aplikacji wymagających tych konkretnych ograniczeń. Wykonywanie samych ograniczeń przez unikanie tego ograniczenia.

+0

To jest interesujące. W pytaniu OP, czy ten interfejs byłby implementowany według typu, który definiowałby jego wydarzenie, czy byłby realizowany przez typ przekazywany w zdarzeniu? Zasadniczo, w jaki sposób byłoby to wykorzystane w rozwiązaniu OP? – Jim

+0

@ Jim: Zobacz powyższą edycję. Nie można używać zdarzeń, ale można osiągnąć podobne efekty za pomocą interfejsów.Wzorzec używany przez 'IObservable' /' IObserver' może być dobrą alternatywą dla używania zdarzeń. – supercat

+0

@ subperate - Prawo Mam tutaj wzorzec obserwatora, ale czy typ otaczający nadal nie zdefiniowałby parametrów IThis, IThat lub sam też określił te same parametry? – Jim

4

W gruncie rzeczy tworzysz instancję tego delegata. Instancje muszą mieć zdefiniowane typy ogólne.

Definicja swojego delegata może zawierać T, ale czy instancja musi określić, które T.

+2

Lub być w ogólnej klasie, która definiuje 'T' – payo

+0

. Czy istnieje jakiś sposób na przekazanie parametru ogólnego w zdarzeniu (bez określenia typu T w czasie kompilacji, który zdaje się zabierać mi punkt generyczny?)? – Xenoprimate

+0

Prawdą jest, że w tym przypadku T jest definiowane przez instancję klasy, więc definiowana jest również instancja delegata, co jest zgodne z tym, co napisałem. –

3

Biorąc pod uwagę ten przykład:

public delegate void FooDelegate<T>(T value); 

public class FooContainer 
{ 
    public event FooDelegate<T> FooEvent; 
} 

Kompilator jak w przykładzie nie lubi deklarację FooEvent ponieważ T nie jest zdefiniowana. Jednak zmiana FooContainer do

public delegate void FooDelegate<T>(T value); 

public class FooContainer<T> 
{ 
    public event FooDelegate<T> FooEvent; 
} 

A teraz kompilator jest w porządku z tym, ponieważ ten, kto tworzy instancje FooContainer będą musiały określić typ T jak tak

FooContainer<string> fooContainer = new FooFooContainer<string>(); 

Jednak można również ograniczyć T do taki interfejs.

public delegate void FooDelegate<T>(T value) where T : IFooValue; 

public class FooContainer 
{ 
    public event FooDelegate<IFooValue> FooEvent; 

    protected void OnFooEvent(IFooValue value) 
    { 
     if (this.FooEvent != null) 
      this.FooEvent(value); 
    } 
} 

public interface IFooValue 
{ 
    string Name { get; set; }// just an example member 
} 

W tym przypadku można podnieść zdarzenia z typów dopóki implementować interfejs IFooValue.