2010-09-17 13 views
5

Mi delegado parece no aceptar una subclase, creo que un ejemplo es el más fácil.¿El delegado no acepta la subclase?

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

Cuando cambio de prueba para aceptar B que arroja un error de compilación. ¿No es extraño porque B se extiende A?

compilador de error:

No hay sobrecarga para la prueba coincide con devolución de llamada

¿Hay una manera de hacer que mi delegado acepta una clase que se extiende A?

+0

lo hace lanzar una excepción o se abstiene en la compilación? – Elisha

+1

Eso no debería estar causando un problema. ¿Puedes pegar aquí el código modificado? ¿Solo para saber cómo lo estás cambiando? –

+0

@Mamta Dalal - Edité mi pregunta, espero que esto lo aclare un poco más. – Kevin

Respuesta

3

No es extraño, porque si usted tiene un objeto de clase C que se extiende A, no tendría sentido para pasar a Test() si sólo se acepta una B. Cualquier método utilizado para un Callback tiene que aceptar cualquierA, no solo una subclase específica. Debería cambiar la firma del delegado Callback para aceptar B si desea Test() para aceptar B también.

class C : A {}; 

Callback callback = Test; 

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

Entonces, ¿mi única opción sería convertir 'A' a' B' dentro de 'Test'? – Kevin

+0

@Kevin: No, no es la única opción. Una mejor opción es declarar al delegado como 'void CallBack (B b)'. Todavía puede usar el método 'Test (A a)' luego. – Timwi

+0

@Timwi - No puedo hacer eso, me temo. Tengo un 'Evento' de clase que todos pueden extender y pasar al delegado. – Kevin

-1

C# support both covariance and contravariance delegados, por lo que esto debería funcionar.

El problema es la sobrecarga.

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

Qué sobrecargue método de firma (Test(A a) o Test(B b)) tiene que ser resuelto en tiempo de compilación - sin embargo, ambos podrían aplicar, por lo que se genera un error.

Se puede evitar esto mediante el fraccionamiento de la sobrecarga:

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

En cualquiera de los casos puede pasar un 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()); 

Dado que usted tiene esta contravarianza, ¿por qué necesita las sobrecargas ?

+0

Estoy intentando recrear el modelo eventlistener de Flash en C# :) - ¡Gracias por la respuesta! El mayor problema es que los usuarios necesitan poder extender mi clase 'Event' y pasar su clase al delegado. – Kevin

+0

la contravarianza solo funciona cuando el argumento es un tipo ** menos derivado ** que el de la declaración del delegado. Nunca puede asignar un método tomando un argumento 'B' al delegado que ha declarado. Por otro lado, si el delegado fue declarado para tomar un argumento 'B', usted podría agregar un método de argumento' A' –

1

Es bastante fácil de entender.Ahora tenemos:

class A { } 
class B : A { } 

Escenario 1 al principio

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

Escenario 2CallBack(A a) y Test(B b)

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

Escenario 3CallBack(B b) y Test(A a)

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

Gracias por aclararlo. Supongo que tendré que lanzar 'A' a 'B' entonces, qué mal ... – Kevin

8

No es esto extraño porque B se extiende A?

Tiene la idea correcta, pero en la dirección incorrecta. Consideremos un ejemplo más fácil de razonar:

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

Eso es claramente legal. dm necesita ser un método que tome un mamífero, y lo es.

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

Eso claramente tiene que ser ilegal. No se puede asignar un método que lleve un tigre a un delegado que se lleva a un mamífero, porque un delegado que toma un mamífero puede tomar cualquier mamífero, no solo un tigre. Si esto fuera legal, entonces sería posible pasar una jirafa a un método que tome un tigre.

¿Qué tal esto?

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

Eso está bien. da es un delegado de un método que toma cualquier mamífero. Un método que toma cualquier animal claramente también toma cualquier mamífero. Puede asignar DoAnimal (Animal) a un delegado D (Mamífero) porque Mamífero extiende Animal. ¿Ahora ves cómo obtuviste la dirección de extensión hacia atrás?

tipos de retorno en el otro trabajo de la mano la forma de pensar que hacen:

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

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

No hay problema.

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

No hay problema; GetTiger devuelve un Tiger, por lo que puede asignarlo a un delegado que requiere que su objetivo devuelva un mamífero.

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

Eso no es bueno. GetAnimal podría devolver una Serpiente, y ahora tienes una variable escrita como Mamífero que contiene una Serpiente. Esto tiene que ser ilegal.

Esta función se denomina "covarianza y contravarianza de las conversiones de grupo de miembros" y se introdujo en C# 2.0. Para obtener más información sobre este tema, ver mi artículo sobre el mismo:

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

Cuestiones relacionadas