2011-02-03 10 views
20

Me pregunto si dynamic es semánticamente equivalente a object cuando se utiliza como un parámetro de tipo genérico. Si es así, tengo curiosidad por saber por qué existe esta limitación, ya que ambas son diferentes cuando se asignan valores a variables o parámetros formales.¿Por qué "dinámico" no es covariante y contravariante con respecto a todos los tipos cuando se usa como parámetro de tipo genérico?

He escrito un pequeño experimento en C# 4.0 para desvelar algunos de los detalles. He definido algunas interfaces e implementaciones simples:

interface ICovariance<out T> { T Method(); } 

interface IContravariance<in T> { void Method(T argument); } 

class Covariance<T> : ICovariance<T> 
{ 
    public T Method() { return default(T); } 
} 

class Contravariance<T> : IContravariance<T> 
{ 
    public void Method(T argument) { } 
} 

Los detalles interesantes del experimento:

class Variance 
{ 
    static void Example() 
    { 
     ICovariance<object> c1 = new Covariance<string>(); 
     IContravariance<string> c2 = new Contravariance<object>(); 

     ICovariance<dynamic> c3 = new Covariance<string>(); 
     IContravariance<string> c4 = new Contravariance<dynamic>(); 

     ICovariance<object> c5 = new Covariance<dynamic>(); 
     IContravariance<dynamic> c6 = new Contravariance<object>(); 

     // The following statements do not compile. 
     //ICovariance<string> c7 = new Covariance<dynamic>(); 
     //IContravariance<dynamic> c8 = new Contravariance<string>(); 

     // However, these do. 
     string s = new Covariance<dynamic>().Method(); 
     new Contravariance<string>().Method((dynamic)s);  
    } 
} 

Los primeros dos estados con c1 y c2 demuestran que la covarianza básica y contravarianza están trabajando. Luego uso c3 y c4 para mostrar que dynamic se puede usar como un parámetro de tipo genérico de la misma manera.

Las declaraciones con c5 y c6 revelan que una conversión de dynamic a object es siempre válida. Esto no es realmente sorprendente, ya que object es un antepasado de todos los demás tipos.

El experimento final con c7 y c8 es donde empiezo a confundirme. Implica que los métodos que devuelven los objetos dynamic no son sustitutos de los métodos que devuelven string, y de manera similar que los métodos que aceptan objetos string no pueden tomar dynamic. Las dos declaraciones finales con la asignación y la llamada al método muestran que claramente este no es el caso, de ahí mi confusión.

pensé en esto un poco, y se preguntó si esto es para evitar que los programadores de usar ICovariance<dynamic> como un escalón entre las conversiones de tipos que puedan resultar en errores de ejecución, tales como:

ICovariance<dynamic> c9 = new Covariance<Exception>(); 
ICovariance<string> c10 = c9; 
// While this is definitely not allowed: 
ICovariance<string> c11 = new Covariance<Exception>(); 

Sin embargo, esto es poco convincente en el caso de dynamic desde perdemos seguridad de tipos de todos modos:

dynamic v1 = new Exception(); 
string v2 = v1; 

Dicho de otra manera, la pregunta es "¿por qué la semántica de dynamic difieren entre asignación y covarianza/contravarianza con los genéricos? "

+1

espero que Eric nos visite y nos dé revelaciones :) – Andrey

+3

Estoy lanzando 'Summon Eric Lippert' tan fuerte como puedo. – Amy

+1

deberías decir "Casting 'Summon Eric Lippert' tan fuerte como puedo." – Andrey

Respuesta

23

Me pregunto si dynamic es semánticamente equivalente a object cuando se usa como un parámetro de tipo genérico.

Su conjetura es completamente correcta.

"dinámico" como un tipo no es más que "objeto" con un sombrero divertido, un sombrero que dice "en lugar de hacer una comprobación de tipo estático para esta expresión de tipo objeto, generar código que hace la comprobación de tipo en tiempo de ejecución ". En todos los demás aspectos, la dinámica es solo objeto, fin de la historia.

Tengo curiosidad por saber por qué existe esta limitación ya que ambas son diferentes cuando se asignan valores a variables o parámetros formales.

Piénselo desde la perspectiva del compilador y luego desde la perspectiva del verificador de IL.

Cuando asigna un valor a una variable, el compilador básicamente dice "Necesito generar código que realice una conversión implícita desde un valor de tal tipo al tipo exacto de la variable". El compilador genera código que hace eso, y el verificador IL verifica su corrección.

Es decir, el compilador genera:

Frob x = (Frob)whatever; 

pero limita las conversiones para las conversiones implícitas, conversiones no explícitos.

Cuando el valor es dinámico, el compilador básicamente dice "Necesito generar código que interrogue a este objeto en tiempo de ejecución, determine su tipo, inicie nuevamente el compilador y escuche un pequeño fragmento de IL que convierta cualquier objeto es del tipo de esta variable, ejecuta ese código y asigna el resultado a esta variable. Y si algo falla, tira ".

Es decir, el compilador genera el equivalente moral de:

Frob x = MakeMeAConversionFunctionAtRuntime<Frob>((object)whatever); 

El verificador ni siquiera parpadea en eso. El verificador ve un método que devuelve un Frob. Ese método podría lanzar una excepción si no puede convertir "lo que sea" en un Frob; de cualquier forma, nada más que un Frob se escribe en x.

Ahora piense en su situación de covarianza. Desde la perspectiva de CLR, no existe tal cosa como "dinámico". En cualquier lugar donde tengas un argumento de tipo que sea "dinámico", el compilador simplemente genera "objeto" como argumento de tipo. "dinámico" es una característica de lenguaje C#, no una característica Common Language Runtime. Si la covarianza o contravarianza en "objeto" no es legal, tampoco es legal en "dinámico". No hay IL que el compilador pueda generar para hacer que el sistema de tipos de CLR funcione de manera diferente.

Esto explica por qué observa que hay una conversión de, por ejemplo, List<dynamic> ay de List<object>; el compilador sabe que son del mismo tipo. La especificación en realidad indica que estos dos tipos tienen una identidad conversión entre ellos; son tipos idénticos.

¿Tiene todo eso sentido? Pareces muy interesado en los principios de diseño que son muy dinámicos; en lugar de tratar de deducirlos de los primeros principios y experimentos usted mismo, podría ahorrarse la molestia y leer Chris Burrows' blog articles on the subject. Hizo la mayor parte de la implementación y una buena cantidad del diseño de la función.

+0

Gracias por la explicación en profundidad. Parece que esto fue menos un problema de PL/teoría de tipos y más influenciado por las decisiones de ingeniería y el CLR. – ide

+4

@ide: no hay ninguna teoría de tipos con "dinámico". Se trata de hacer que sea más fácil para los programadores de C# interoperar con modelos de objetos de otros idiomas. La intención no es hacer de "dinámico" un tipo de primera clase en el sistema de tipos; la intención es hacer menos destructivo el escribir el código C# que habla con Python/Ruby/COM/Excel/cualquier objeto. –

2

para éste:

ICovariance<string> c7 = new Covariance<dynamic>(); 

razón es obvia, si era posible, entonces usted podría hacer:

c7.Method().IndexOf(...); 

y que sin duda no, salvo que dynamic no es string o tiene esos método

ya que (incluso después de todos los cambios) C# no es un lenguaje dinámico. La covarianza solo está permitida cuando es definitivamente segura. Por supuesto, puedes ponerte en pie y llamar al IndexOf en la variable dynamic, pero no puedes permitir que los usuarios de tu API lo hagan sin querer. Por ejemplo, si devuelve un ICovariance<string> con dynamic, el código de llamada secreta puede fallar.

recordar la regla, D es covariante a B si hay un CAST de D a B. En este caso, no hay conversión del dynamic al string.

Pero dynamic es covariante a object simplemente porque todo se deriva de ello.

+0

Pero pierden la seguridad de tipo con' dynamic' de todos modos. 'dynamic d = 0; d.IndexOf (...);'. Mi pregunta es por qué este comportamiento no se extiende a los genéricos. – ide

+0

@ide estaba escribiendo la explicación cuando escribiste el comentario :) básicamente es porque los llamantes de su código no tienen idea de lo que hay en su interfaz. Esperan cadena y no les importa. – Andrey

+0

@ide thing es curioso que ese tipo de seguridad no se pierde en absoluto. Simplemente proporciona azúcar de sintaxis para diferentes tipos de despacho dinámico. Por ejemplo reflexión. Es por eso que me parece '' dinámico '' poco significativo a excepción de la interoperabilidad COM. – Andrey

0

"¿Por qué la semántica de la dinámica difiere entre asignación y covarianza/contravarianza con genéricos?"

La respuesta es que al usar genéricos se abstrae del tipo de datos en sí. Sin embargo, también implica que genérico es lo suficientemente genérico como para que todos los tipos compartan la misma funcionalidad.

Así que si tiene 'ICovariance c9 = new Covariance(); `tanto dinámico como de excepción no tienen las mismas funcionalidades (como tipos de base). Además, el compilador no tiene idea de cómo convertir de dinámico a excepción (aunque ambos heredan del objeto).

Si hubiera una jerarquía de herencia explícita entre dynamic y Exception (que no sea el objeto), entonces esto sería algo bueno.

La razón es un poco porque puedes abatir, pero no subir. Por ejemplo, si la excepción hereda de la dinámica, entonces estaría bien. Si Dynamic hereda de Exception, sería un upcast un poco diferente y eso no estaría bien, ya que podría haber una condición en la que exista la "Excepción 's data is not present in dinámica".

.NET tiene estos typecasts explícitos incorporados, y puede verlos en acción en el objeto System.Convert. Sin embargo, los tipos que son súper específicos no se pueden convertir implícita o explícitamente fácilmente entre sí sin un código personalizado. Y esta es una de las razones por las que fallan los tipos múltiples (como es el caso de 'ICovariance c9 = new Covariance(); `). Esto también está diseñado para preservar la seguridad del tipo.

1

Porque las palabras clave dinámicas y covariantes/contravariantes son tan nuevas?

Supongo que respondió su propia pregunta. El tipo de seguridad de asignación se relaja en las declaraciones de asignación, porque así es como funciona el dinámico; cortocircuita la comprobación de tipos en tiempo de compilación para que pueda realizar asignaciones que ESPERE que funcione desde objetos sobre los que el compilador no tiene ni idea.

Sin embargo, la covarianza/contravarianza genérica está rígidamente controlada; sin el uso de las palabras clave de entrada/salida (que también se introdujeron junto con la dinámica en C# 4.0) no se pudo convertir de ninguna manera. Los parámetros genéricos, incluso con co/contravarianza permitida, requieren que los tipos estén en la misma rama de la jerarquía de herencia. Una Cadena no es una dinámica y una dinámica no es una cadena (aunque ambas son Objetos y una dinámica puede referirse a lo que se puede acceder como una cadena), por lo que la verificación de tipo genérica inherente a las comprobaciones de covarianza/contravarianza falla, mientras OTOH , al compilador se le dice expresamente que ignore la mayoría de las operaciones no genéricas que implican dinámica.

Cuestiones relacionadas