2010-09-17 2 views
5

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

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

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

Compiler ошибки:

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

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

+0

это бросить ли исключение или не в компиляции? – Elisha

+1

Это не должно вызывать проблемы. Можете ли вы вставить здесь измененный код? Просто знать, как вы его меняете? –

+0

@Mamta Dalal - отредактировал мой вопрос, надеюсь, что это еще раз очистит его. – Kevin

ответ

3

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

class C : A {}; 

Callback callback = Test; 

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

Таким образом, мой единственный вариант - отдать 'A' на' B' внутри 'Test'? – Kevin

+0

@Kevin: Нет, это не единственный вариант. Лучшим вариантом является объявление делегата как 'void CallBack (B b)'. Вы все равно можете использовать метод 'Test (A a)'. – Timwi

+0

@ Тимви - Я не могу этого сделать, я боюсь. У меня есть класс 'Event', который каждый может продлить и передать делегату. – Kevin

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

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

+0

Я пытаюсь воссоздать модель вспышки Flash в C# :) - Спасибо за ответ! Самая большая проблема заключается в том, что пользователи должны иметь возможность расширить класс 'Event' и передать свой класс делегату. – Kevin

+0

Контравариантность работает только тогда, когда аргумент является ** менее производным **, чем тот, что указан в декларации делегата. Вы никогда не можете назначить метод, принимающий аргумент 'B' делегату, который вы объявили. С другой стороны, если делегат был объявлен для принятия аргумента 'B', вы могли бы добавить метод аргумента' A' –

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 

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

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

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

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

Спасибо за его очистку. Наверное, мне придется бросать 'A' на' B', тогда тоже плохо. – Kevin

8

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

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

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

Смежные вопросы