2010-09-17 17 views
5

Mój delegat nie wydaje się akceptować podklasy, myślę, że przykład jest najłatwiejszy.Delegat nie akceptuje podklasy?

public class A 
{ 
    public A() { } 
} 

public class B : A 
{ 
    public B() { } 
} 

public class Program 
{ 
    private delegate void CallBack(A a); 
    private static CallBack callBack = new CallBack(Test); 

    public Main(string[] args) 
    { 
      callBack(new B()); 
    } 

    private static void Test(A a) 
    { 
      Console.WriteLine("Test()");  
    } 

    // Compilation error occurs if Test becomes: 
    private static void Test(B a) 
    { 
      Console.WriteLine("Test()"); 
    } 
} 

Po zmianie testu na powoduje błąd kompilacji. Czy to nie dziwne, ponieważ B rozszerza się o A?

Compiler błąd:

nr przeciążenie dla testu mecze oddzwaniania

Czy istnieje sposób, aby mój delegat przyjąć klasę, która rozciąga A?

+0

Czy to wyjątek lub nie w kompilacji? – Elisha

+1

To nie powinno powodować problemu. Czy możesz tutaj wkleić zmieniony kod? Po prostu wiesz, jak to zmieniasz? –

+0

@Mamta Dalal - Zmieniono moje pytanie, mam nadzieję, że to wyjaśni nieco więcej. – Kevin

Odpowiedz

3

To nie jest dziwne, bo jeśli obiekt klasy C który rozciąga A, to nie ma sensu przechodzić do Test() jeśli tylko zaakceptuje B. Dowolna metoda używana w przypadku metody Callback musi akceptować każdą podklasę. Będziesz musiał zmienić podpis delegata Callback, aby zaakceptować B, jeśli chcesz, aby Test() zaakceptował także B.

class C : A {}; 

Callback callback = Test; 

callback(new C()); //what if Test() accepted B??? 
+0

Więc moją jedyną opcją byłoby wrzucenie 'A' do' B' wewnątrz 'Test'? – Kevin

+0

@Kevin: Nie, to nie jedyna opcja. Lepszym rozwiązaniem jest zadeklarowanie delegata jako 'void CallBack (B b)'. Nadal możesz używać metody "Test (A)". – Timwi

+0

@ Timwi - Nie mogę tego zrobić, obawiam się. Mam klasę "Event", którą każdy może rozszerzyć i przekazać delegatowi. – Kevin

-1

C# delegaci support both covariance and contravariance, więc powinno to zadziałać.

Problem stanowi przeciążenie.

// this delegate supports contravariance - and subclass of A should work 
delegate void CallBack(A a); 

// however this can't pick up either Test because both could be used 
static CallBack callBack = new CallBack(Test); 

Które przeciążać metody podpis (Test(A a) lub Test(B b)) musi być rozwiązany w czasie kompilacji - jednak oba mogą mieć zastosowanie, więc zostanie zgłoszony błąd.

Można tego uniknąć poprzez podział na przeciążenie:

static void TestA(A a) 
{ 
     Console.WriteLine("Test(a)");  
} 

// Compilation error occurs if Test becomes: 
static void TestB(B a) 
{ 
     Console.WriteLine("Test(b)"); 
} 

// this is valid because it's an exact match 
static CallBack callBackA = new CallBack(TestA); 

// this is valid because delegates support contravariance 
static CallBack callBackB = new CallBack(TestB); 

W obu przypadkach można przekazać B:

// B is subclass of A, so can be passed to TestA 
callBackA(new B()); 

// CallBack supports contravariance, so can call TestB 
callBackB(new B()); 

Biorąc pod uwagę, że masz ten kontrawariancji, dlaczego trzeba się przeciążeń ?

+0

Próbuję odtworzyć model zdarzeń Flasha w C# :) - Dzięki za odpowiedź! Największym problemem jest to, że użytkownicy muszą mieć możliwość rozszerzenia mojej klasy "Event" i przekazania swojej klasy delegatowi. – Kevin

+0

Kontrawariancja działa tylko wtedy, gdy argument jest typu ** mniej pochodnego ** niż ten w deklaracji delegata. Nigdy nie można przypisać metody przyjmującej argument "B" do zadeklarowanego delegata. Z drugiej strony, jeśli delegat zostanie zadeklarowany jako argument "B", będziesz mógł dodać metodę argumentu "A" –

1

Jest to dość łatwe do zrozumienia.Teraz mamy:

class A { } 
class B : A { } 

Scenariusz 1 na początku

public delegate void CallBack(A a); 
public void Test(A a) { } 
CallBack cb = new CallBack(Test); 
cb(new A()); //good and easy usage 

Scenariusz 2CallBack(A a) i Test(B b)

//compile error, because Test(B b) has a smaller argument scope than CallBack 
//CallBack cb = new CallBack(Test); 

Scenariusz 3CallBack(B b) i Test(A a)

CallBack cb = new CallBack(Test); 
cb(new A()); //no error, becasue B can convert to A 
+0

Dzięki za wyczyszczenie. Sądzę, że będę musiał rzucić "A" na "B" wtedy, szkoda ... – Kevin

8

Czy to nie jest dziwne, bo B rozszerza?

Masz właściwy pomysł, ale w złym kierunku. Rozważmy przykład, który jest łatwiejszy do zrozumienia:

class Animal {} 
class Reptile : Animal {} 
class Snake : Reptile {} 
class Mammal : Animal {} 
class Tiger : Mammal {} 
class Giraffe : Mammal {} 
delegate void D(Mammal m); 
static void DoAnimal(Animal a) {} 
static void DoMammal(Mammal m) {} 
static void DoTiger(Tiger t) {} 

D dm = DoMammal; 
dm(new Tiger()); 

To jest oczywiście legalne. dm musi być metodą, która bierze Ssak i jest.

D dt = DoTiger; 
dt(new Giraffe()); 

To musi być nielegalne. Nie można przypisać metody, która zabiera tygrysa do delegata, który zabiera ssaka, ponieważ delegat zabierający ssaka może wziąć dowolnego ssaka, a nie tylko tygrysa. Gdyby to było zgodne z prawem, możliwe byłoby przekazanie żyrafy do metody, która zabiera tygrysa.

Co z tym?

D da = DoAnimal; 
da(new Giraffe()); 

W porządku. da jest delegatem metody, która przyjmuje dowolnego ssaka. Metoda, która przyjmuje dowolne zwierzę, wyraźnie bierze również dowolnego ssaka. Możesz przypisać DoAnimal (Zwierzę) do delegata D (Ssak), ponieważ Ssak rozciąga Zwierzę. Widzisz teraz, w jaki sposób przesunąłeś kierunek przedłużenia do tyłu?

typy powrotu na drugiej pracy ręcznej sposób myślenia robią:

delegate Mammal F(); 
static Animal GetAnimal() {...} 
static Mammal GetMammal() {...} 
static Tiger GetTiger() {...} 

F fm = GetMammal; 
Mammal m = fm(); 

nie ma problemu.

F ft = GetTiger; 
Mammal t = ft(); 

Nie ma problemu; GetTiger zwraca Tiger, więc możesz przypisać go do delegata, który wymaga, aby jego cel zwrócił ssaka.

F fa = GetAnimal; 
Mammal a = fa(); 

To nie jest dobre. GetAnimal może zwrócić Snake, a teraz masz zmienną wpisaną jako Mammal, która zawiera Snake. To musi być nielegalne.

Ta funkcja jest nazywana "kowariancją i kontrawariancją konwersji grup członków" i została wprowadzona w języku C# 2.0. Aby uzyskać więcej informacji na ten temat patrz mój artykuł na jej temat:

http://blogs.msdn.com/b/ericlippert/archive/2007/10/19/covariance-and-contravariance-in-c-part-three-member-group-conversion-variance.aspx