2010-02-26 11 views
7

¿Por qué esto no funciona? ¿No entiendo correctamente la covarianza de delegados?Delegate Covariance Confusion Enigma!

public delegate void MyDelegate(object obj) 

public class MyClass 
{ 
    public MyClass() 
    { 
     //Error: Expected method with 'void MyDelegate(object)' signature 
     _delegate = MyMethod; 
    } 

    private MyDelegate _delegate; 

    public void MyMethod(SomeObject obj) 
    {} 

} 
+4

Amplia aliteración siempre divierte – Bob

+0

Intenté responder a estas preguntas en una breve publicación de preguntas frecuentes: http://blogs.msdn.com/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx Todavía hay mucha confusión en torno a esta característica ... –

Respuesta

12

correcta - no entiende correctamente covarianza - todavía :) Su código funcionaría si tuviera el mismo tipo de retorno, sino como valores, así:

public delegate object MyDelegate() 

public class MyClass 
{ 
    public MyClass() 
    { 
     _delegate = MyMethod; 
    } 

    private MyDelegate _delegate; 

    public SomeObject MyMethod() { return null; } 
} 

que demostrarían covarianza . Alternativamente, se puede mantener como parámetros, pero cambiar los tipos en torno a:

public delegate void MyDelegate(SomeObject obj) 

public class MyClass 
{ 
    public MyClass() 
    { 
     _delegate = MyMethod; 
    } 

    private MyDelegate _delegate; 

    public void MyMethod(object obj) {} 
} 

Esto demuestra ahora contravarianza.

Mi regla de oro es preguntarme a mí mismo: "dado el delegado, ¿qué podría hacer con él? Si puedo pasar un argumento que rompería el método, la conversión debería haber fallado. Si el método puede devolver algo que rompería el llamador, la conversión debería haber fallado. "

En el código, podría haber llamado:

_delegate(new object()); 

En ese momento, la mala MyMethod tiene un parámetro que es significaba a ser de tipo SomeObject, pero es en realidad de tipo object. Esto sería algo muy malo, por lo que el compilador evita que suceda.

hace todo más sentido?

+0

Esto no resuelve el problema del código original donde un delegado genérico debe implementarse de una manera más específica. Este es el punto de los genéricos en el lenguaje. –

+0

¡Lo tenía al revés y ahora tiene mucho más sentido! El tipo más genérico debe ser un parámetro en el método que no esté dentro del delegado. ¡Gracias! –

+0

@Payton Byrd: ¿Qué te hace pensar que ese es el problema del código original? Supuse que el OP quería saber por qué su código no funcionaba, dado que esa era la pregunta que hacía. –

1

El tipo MyDelegate declara que puede pasar cualquier tipo de objeto. Sin embargo, MyMethod sólo toma los objetos de tipo SomeObject. ¿Qué sucede si trato de invocar al delegado que pasa un tipo diferente de objeto: _delegate("a string object")? De acuerdo con la declaración de MyDelegate, esto debería permitirse, pero su función MyMethod no puede recibir un argumento de cadena.

4

Es necesario utilizar un genérico.

EDIT: ¿Por qué? Porque como otro cartel señaló, objeto y no lo hacen SomeObject equivale a lo mismo que el objeto puede ser no SomeObject. Este es el punto de Generics en el idioma.

public delegate void MyDelegate<T>(T obj) 

public class MyClass 
{ 
    public MyClass() 
    { 
     _delegate = MyMethod; 
    } 

    private MyDelegate<SomeObject> _delegate; 

    public void MyMethod(SomeObject obj) 
    { 
    } 
} 
+0

¡Tiene razón, esta era mi intención original con el código! Analicé lo que ofrece 4.0 y permitirán algunas cosas geniales para delegados genéricos: http://blog.tlk.com/dot-net/2009/c-sharp-4-covariance-and-contravariance –

+1

@Adam: usted debe tener en cuenta que la variación que ha estado viendo en su publicación no es nueva en C# 4.0; está disponible en C# 2.0. Es solo la varianza * genérica * que es nueva en C# 4. –

4

Los argumentos son contravariant, regresan tipos son covariantes. Si se llamara al delegado con un object que no es una instancia de SomeObject, tendría un error de tipeo. Por otro lado, devolver SomeObject desde una rutina envuelta en un delegado que devuelve object está bien.

1

Desde el enlace MSDN que proporcionan

covarianza permite un método para tener una más derivados de retorno tipo de lo que es se define en el delegado. Contravarianza permite un método con tipos de parámetros que son menos derivados que en el tipo de delegado.

usted está tratando de utilizar un derivado más parámetro de tipo que no es compatible (aunque .NET 4.0 probablemente ya que este ha resuelto muchos problemas de covarianza/contravarianza).

+0

12 de abril ¿verdad? Pronto, pronto, pronto ... –

+1

No, este código tampoco funciona en C# 4.0. De lo contrario, podría pasar "objeto" a un método que necesita "SomeObject", que no es seguro. –

0

La covarianza y la contradicción se trata de comprender el Is-a-Principle of inheritance.

En ambos, covarianza y contravarianza, s.th. se "transfiere", ya sea como valor de retorno o como argumento para el método delegado. Lo que se "traspasa" tiene que ser "atrapado" en un receptáculo. En C# - o la jerga de programación como tal - usamos el término cubo para lo que llamé receptáculo. A veces hay que recurrir a otras palabras para captar el significado de las palabras de jerga comúnmente utilizadas.

De todos modos, si usted entiende la herencia, que probablemente cualquier lector aquí, entonces, lo único a lo que prestarle atención es que el receptáculo, i. mi. el cubo utilizado para la captura debe ser del mismo tipo o de un tipo menos derivado que el que se está pasando; esto es cierto tanto para la covarianza como para la contravarianza.

La herencia dice que puedes atrapar un pájaro en un cubo de animal porque el pájaro es un animal. Entonces, si un parámetro de un método tiene que atrapar un pájaro, se puede atrapar en un cubo de animal (un parámetro de tipo animal), que entonces es una contravariancia. Y si su método, es decir, su delegado devuelve un pájaro, entonces el "cubo" también puede ser un tipo de ave o menos derivado (de un tipo principal) lo que significa que la variable donde captura el valor de retorno del método debe ser el mismo tipo o menos derivado que el valor de retorno.

Simplemente cambie su forma de pensar para discriminar entre lo que se está pasando y lo que atrapa, ya que entonces toda la complejidad sobre la covarianza y la contradicción se disuelve muy bien. Entonces te das cuenta de que el mismo principio está en el trabajo. Es solo que la herencia no puede ser violada, ya que fluye de una sola manera.

Y el compilador es tan inteligente que cuando lanza el depósito en el tipo más especializado (de nuevo, y según sea necesario) solo entonces obtiene todos los métodos especializados que se agregaron a la clase más derivada. Esa es la belleza de eso. Por lo tanto, es atrapar, lanzar y usar lo que tienes y tal vez necesites.