tag example c# arrays types command-line-interface

example - ¿Por qué mi matriz de C#pierde la información de signo de tipo cuando se convierte en objeto?



c# summary example (4)

Al investigar un error, descubrí que se debía a esta rareza en c #:

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 yo hubiera esperado que "la bar is byte[] " para devolver Falso. Aparentemente la barra es tanto un byte[] como un sbyte[] ? Lo mismo sucede con otros tipos con signo / sin signo como Int32[] vs UInt32[] , pero no para, por ejemplo, Int32[] vs Int64[] .

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


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

Vea los comentarios del blog para una discusión extendida de este tema. Gracias por la gran pregunta!

Ha tropezado con una incoherencia interesante y desafortunada entre el sistema de tipo CLI y el sistema de tipo C #.

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

El sistema de tipo CLI dice, por ejemplo, que los subtipos del tipo de referencia son compatibles con la asignación de supertipos del 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 contrario no es cierto; Los supertipos no son compatibles con la asignación de subtipos. No se puede pegar algo que solo se sabe que es un objeto en una variable de tipo cadena sin lanzarlo primero.

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

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

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

Es decir, las matrices son covariantes con respecto a la compatibilidad de la asignación. Esto es en realidad un tipo de covarianza rota; 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

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

En la CLI, uint y int son compatibles con la asignación. Pero en C #, la conversión entre int y uint es EXPLICIT, no IMPLICIT, y estos son tipos de valor, no tipos de referencia. Así que en C #, no es legal convertir un int [] a un uint [].

Pero es legal en el CLI. Así que ahora nos enfrentamos con una elección.

1) Implementar "es" para que cuando el compilador no pueda determinar la respuesta de forma estática, en realidad llame a un método que verifique todas las reglas de C # para determinar la convertibilidad que preserva la identidad. Esto es lento, y el 99.9% del tiempo coincide con lo que son las reglas de CLR. Pero tomamos el impacto de rendimiento para cumplir al 100% con las reglas de C #.

2) Implementar "es" para que cuando el compilador no pueda determinar la respuesta de forma estática, realice la comprobación de compatibilidad de la asignación de CLR increíblemente rápida, y viva con el hecho de que esto dice que una uint [] es una int [], aunque eso En realidad no ser legal en C #.

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


Pasó 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 # está optimizando las dos primeras comparaciones ( foo is sbyte[] y foo is byte[] ). Como puede ver, se han optimizado para foo != null y simplemente siempre son false .


Seguramente la salida es correcta. bar "is" tanto sbyte [] como byte [], porque es compatible con ambos, dado que bar no es más que un objeto, entonces "podría estar" firmado o sin firmar.

"is" se define como "la expresión se puede convertir para escribir".


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