2009-07-24 7 views
42

Investigando un error, he descubierto que era debido a esta rareza en C#:¿Por qué mi matriz C# pierde la información del signo de tipo cuando se envía al objeto?

sbyte[] foo = new sbyte[10]; 
object bar = foo; 
Console.WriteLine("{0} {1} {2} {3}", 
     foo is sbyte[], foo is byte[], bar is sbyte[], bar is byte[]); 

La salida es "Verdadero Falso Verdadero Verdadero", mientras que lo que habría esperado "bar is byte[]" para volver Falso. Al parecer, la barra es a la vez byte[] y sbyte[]? Lo mismo ocurre con otros tipos con firma/sin firmar como Int32[] frente a UInt32[], pero no para decir Int32[] contra Int64[].

¿Alguien puede explicar este comportamiento? Esto está en .NET 3.5.

Respuesta

65

ACTUALIZACIÓN: He usado esta pregunta como base para una entrada de blog, aquí:

http://blogs.msdn.com/ericlippert/archive/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent.aspx

Ver los comentarios del blog para una extensa discusión de este problema. Gracias por la gran pregunta!


Usted ha tropezado con una inconsistencia interesante y lamentable entre el sistema de tipo CLI y el sistema de tipo C#.

La CLI tiene el concepto de "compatibilidad de asignación". Si un valor x del tipo de datos conocidos S es "compatible con la asignación" con una ubicación de almacenamiento particular y de tipo de datos conocidos T, entonces puede almacenar x en y. De lo contrario, hacerlo no es un código verificable y el verificador no lo permitirá.

El sistema de tipo CLI dice, por ejemplo, que los subtipos de tipo de referencia son compatibles con supertipos de tipo de referencia. Si tiene una cadena, puede almacenarla en una variable de tipo objeto, porque ambos son tipos de referencia y la cadena es un subtipo de objeto. Pero lo opuesto no es verdad; los supertipos no son compatibles con subtipos. No se puede pegar algo que se sabe que es un objeto en una variable de tipo cadena sin primero echarlo.

Básicamente "asignación compatible" significa que "tiene sentido pegar estos bits exactos en esta variable". La asignación del valor de origen a la variable de destino debe ser "conservación de representación". Véase mi artículo sobre que para más detalles:

http://ericlippert.com/2009/03/03/representation-and-identity/

Una de las reglas de la CLI es "si X es compatible con la asignación y, entonces x [] es compatible con la asignación Y []".

Es decir, las matrices son covariantes con respecto a la compatibilidad de asignación. Esto es realmente un tipo roto de covarianza; ver mi artículo sobre eso para más detalles.

http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx

Eso no es una regla de C#. La regla de covarianza de la matriz C# es "si X es un tipo de referencia implícitamente convertible a la clase de referencia Y, entonces X [] es implícitamente convertible a Y []". Esa es una regla sutilmente diferente, y por lo tanto su situación confusa.

En la CLI, uint e int son asignaciones de tareas. Pero en C#, la conversión entre int y uint es EXPLÍCITA, no IMPLÍCITA, y estos son tipos de valores, no tipos de referencia. Entonces en C#, no es legal convertir un int [] en un uint [].

Pero ES legal en la CLI. Entonces ahora nos enfrentamos con una elección.

1) Implementar "es" para que cuando el compilador no pueda determinar la respuesta estáticamente, realmente llame a un método que verifique todas las reglas de C# para la convertibilidad que preserve la identidad. Esto es lento, y el 99.9% del tiempo coincide con las reglas de CLR. Pero aprovechamos el rendimiento para ser 100% compatibles con las reglas de C#.

2) Implementar "es" para que cuando el compilador no pueda determinar la respuesta estáticamente, haga la increíblemente rápida verificación de compatibilidad de asignación CLR, y viva con el hecho de que esto indica que uint [] es un int [], a pesar de que eso no sería legal en C#.

Elegimos este último. Es desafortunado que C# y las especificaciones CLI estén en desacuerdo en este punto menor, pero estamos dispuestos a vivir con la inconsistencia.

+0

Gran lectura. Gracias por la respuesta. –

+2

Hola Eric, por curiosidad, ¿decidieron aceptar esta incoherencia o no estaba previsto? Sólo me preguntaba. –

+0

Eliminé mi publicación por deferencia a una respuesta mucho mejor y más profunda. – LBushkin

0

Seguramente la salida es correcta. bar "es" tanto sbyte [] como byte [], porque es compatible con ambos, ya que la barra es simplemente un objeto, entonces "podría ser" firmado o no.

"es" se define como "la expresión se puede convertir a tipo".

+1

Pero dado que 'bar' es del tipo' object', el tipo base de cualquier otro tipo, eso significa que podría decir 'bar is ' y devolvería true, y ese no es el caso. ¿Por qué puedes convertir un 'sbyte []' en 'byte []' solo porque pasa por un tipo de referencia? –

9

ejecutó el fragmento a través de Reflector:

sbyte[] foo = new sbyte[10]; 
object bar = foo; 
Console.WriteLine("{0} {1} {2} {3}", new object[] { foo != null, false, bar is sbyte[], bar is byte[] }); 

El compilador de C# es la optimización de las dos primeras comparaciones (foo is sbyte[] y foo is byte[]). Como puede ver, han sido optimizados para foo != null y simplemente siempre false.

5

También es interesante:

sbyte[] foo = new sbyte[] { -1 }; 
    var x = foo as byte[]; // doesn't compile 
    object bar = foo; 
    var f = bar as byte[]; // succeeds 
    var g = f[0];    // g = 255 
+0

Me falta algo aquí. ¿No es esto lo que esperas? ¿Cuál es la rareza? – cdm9002

+0

No es que 'g = 255', que se espera, pero que' bar as byte [] 'no devuelve null. –

+2

Correcto, entonces, ahora que ha leído mi respuesta, puede deducir que está sucediendo lo mismo aquí. Con el primero, sabemos en tiempo de compilación que esto viola las reglas de C#. Con el segundo, no lo sabemos. Así que tenemos que (1) emitir un método que implementa todas las reglas del lenguaje C# para hacer el reparto, o (2) usar las reglas de lanzamiento de CLR, que son sutilmente diferentes de las reglas de C# para un pequeño porcentaje de casos extraños. Elegimos (2). –

Cuestiones relacionadas