Делегат не принимает подкласс? - PullRequest
5 голосов
/ 17 сентября 2010

Мой делегат, похоже, не принимает подкласс, я думаю, что пример является самым простым.

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()");
     }
 }

Когда я изменяю Test для принятия B, он выдает ошибку компиляции.Разве это не странно, потому что B extends A?

Ошибка компилятора :

Нет перегрузки для тестовых совпадений Обратный вызов

Есть ли способ заставить мой делегат принять класс, который расширяет A?

Ответы [ 4 ]

9 голосов
/ 17 сентября 2010

Разве это не странно, потому что B расширяет A?

У вас правильная идея, но в неправильном направлении.Давайте рассмотрим пример, о котором легче рассуждать:

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());

Это вполне законно.dm должен быть методом, который принимает млекопитающее, и это так.

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

Это явно должно быть незаконно.Вы не можете назначить метод, который берет тигра делегату, который берет млекопитающее, потому что делегат, который берет млекопитающее, может взять любое млекопитающее, не только тигра.Если бы это было законно, то можно было бы передать жирафа методу, который забирает тигра.

Как насчет этого?

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

Хорошо.Да является делегатом метода, который принимает любое млекопитающее.Метод, который берет любое животное ясно, также берет любое млекопитающее.Вы можете назначить DoAnimal (Animal) делегату D (Mammal), потому что Mammal расширяет Animal.Теперь вы видите, как вы получили направление расширения в обратном направлении?

Типы возврата , с другой стороны, работают так, как вы думаете:

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

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

Никаких проблем нет.

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

Никаких проблем там нет;GetTiger возвращает тигра, так что вы можете назначить его делегату, который требует, чтобы его цель вернула млекопитающего.

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

Это не хорошо.GetAnimal может вернуть Snake, и теперь у вас есть переменная типа Mammal, которая содержит Snake.Это должно быть незаконно.

Эта функция называется «ковариация и контравариантность преобразований групп членов» и была введена в C # 2.0.Для получения дополнительной информации по этой теме см. Мою статью:

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

3 голосов
/ 17 сентября 2010

Это не странно, потому что если у вас есть объект класса C, который расширяет A, не имеет смысла переходить на Test(), если он принимает только B.Любой метод, используемый для Callback, должен принимать любой A, а не только определенный подкласс.Вам нужно изменить подпись делегата Callback, чтобы она принимала B, если вы хотите Test(), чтобы также принять B.

class C : A {};

Callback callback = Test;

callback(new C()); //what if Test() accepted B???
1 голос
/ 17 сентября 2010

Это довольно легко понять.Теперь имеем:

class A { }
class B : A { }

Сценарий 1 в начале

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

Сценарий 2 CallBack(A a) и Test(B b)

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

Сценарий 3 CallBack(B b) и Test(A a)

CallBack cb = new CallBack(Test);
cb(new A());  //no error, becasue B can convert to A
0 голосов
/ 17 сентября 2010

C # делегаты поддерживают и ковариацию, и контравариантность , поэтому это должно работать.

Проблема заключается в перегрузке.

 // 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);

Какая сигнатура метода перегрузки (Test(A a) или Test(B b)) должен быть разрешен во время компиляции - однако оба могут применяться, поэтому возникает ошибка.

Этого можно избежать, разделив перегрузку:

 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);

В любом случае вы можете передать 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());

Учитывая, что у вас есть это противоречие, зачем вам перегрузки?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...