Delegate不接受子类?

时间:2010-09-17 07:54:16

标签: c# inheritance delegates

我的委托似乎不接受一个子类,我认为一个例子是最简单的。

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延伸A

编译错误

  

测试没有重载匹配回调

有没有办法让我的委托接受一个扩展A

的类

4 个答案:

答案 0 :(得分:9)

  

这不是奇怪的,因为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());

没关系。 da是一种接受任何哺乳动物的方法的代表。一种明确摄取任何动物的方法也需要任何哺乳动物。您可以将DoAnimal(动物)分配给委托D(哺乳动物),因为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返回一个Tiger,因此您可以将其分配给需要其目标返回哺乳动物的委托。

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

答案 1 :(得分:3)

这并不奇怪,因为如果你有C类的对象扩展A,如果它只接受Test()则传递给B是没有意义的。 1}}。用于Callback的任何方法都必须接受任何 A,而不仅仅是特定的子类。如果您希望Callback同时接受B,则需要更改Test()代理签名以接受B

class C : A {};

Callback callback = Test;

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

答案 2 :(得分:1)

这很容易理解。现在我们有:

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

答案 3 :(得分:-1)

C#委托support both covariance and contravariance,所以这应该有效。

问题是过载。

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

鉴于你有这种逆转,你为什么需要重载?