My delegate doens't seem to accept a subclass, I think an example is the easiest.
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()");
}
// Co开发者_运维技巧mpilation error occurs if Test becomes:
private static void Test(B a)
{
Console.WriteLine("Test()");
}
}
When I change Test to accept B
it throws a compilation error. Isn't this odd because B
extends A
?
Compiler error:
No overload for Test matches Callback
Is there a way to make my delegate accept a class that extends A
?
Isn't this odd because B extends A?
You have the right idea, but in the wrong direction. Let's consider an example that is easier to reason about:
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());
That's clearly legal. dm needs to be a method that takes a Mammal, and it is.
D dt = DoTiger;
dt(new Giraffe());
That's clearly got to be illegal. You cannot assign a method that takes a tiger to a delegate that takes a mammal, because a delegate that takes a mammal can take any mammal, not just a tiger. If this were legal then it would be possible to pass a giraffe to a method that takes a tiger.
What about this?
D da = DoAnimal;
da(new Giraffe());
That's fine. da is a delegate to a method that takes any mammal. A method that takes any animal clearly also takes any mammal. You can assign DoAnimal(Animal) to a delegate D(Mammal) because Mammal extends Animal. You see now how you got the direction of extension backwards?
Return types on the other hand work the way you think they do:
delegate Mammal F();
static Animal GetAnimal() {...}
static Mammal GetMammal() {...}
static Tiger GetTiger() {...}
F fm = GetMammal;
Mammal m = fm();
No problem there.
F ft = GetTiger;
Mammal t = ft();
No problem there; GetTiger returns a Tiger, so you can assign it to a delegate that requires that its target returns a mammal.
F fa = GetAnimal;
Mammal a = fa();
That's no good. GetAnimal might return a Snake, and now you have a variable typed as Mammal that contains a Snake. This has to be illegal.
This feature is called "covariance and contravariance of member group conversions" and it was introduced in C# 2.0. For more information on this topic see my article on it:
https://ericlippert.com/2007/10/19/covariance-and-contravariance-in-c-part-3-method-group-conversion-variance/
It isn't odd because if you have an object of class C
that extends A
, it wouldn't make sense to pass to Test()
if it only accepts a B
. Any method used for a Callback
has to accept any A
, not just a specific subclass. You would need to change the Callback
delegate signature to accept B
if you want to Test()
to accept B
as well.
class C : A {};
Callback callback = Test;
callback(new C()); //what if Test() accepted B???
It's quite easy to understand. Now we have:
class A { }
class B : A { }
Scenario 1 at the beginning
public delegate void CallBack(A a);
public void Test(A a) { }
CallBack cb = new CallBack(Test);
cb(new A()); //good and easy usage
Scenario 2 CallBack(A a)
and Test(B b)
//compile error, because Test(B b) has a smaller argument scope than CallBack
//CallBack cb = new CallBack(Test);
Scenario 3 CallBack(B b)
and Test(A a)
CallBack cb = new CallBack(Test);
cb(new A()); //no error, becasue B can convert to A
C# delegates support both covariance and contravariance, so this should work.
The problem is the overload.
// 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);
Which overload method signature (Test(A a)
or Test(B b)
) has to be resolved at compile time - however both could apply, so an error is thrown.
You could avoid this by splitting out the overload:
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);
In either case you can pass a 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());
Given that you have this contravariance, why do you need the overloads?
精彩评论