¿Cómo se derivan los ValueTypes de Object (ReferenceType) y siguen siendo ValueTypes?

83

C # no permite que las estructuras se deriven de las clases, pero todos los ValueTypes se derivan de Object. ¿Dónde se hace esta distinción?

¿Cómo maneja CLR esto?

Joan Venge
fuente
Resultado de la magia negra de System.ValueTypetipo en el sistema de tipo CLR.
RBT

Respuestas:

108

C # no permite que las estructuras se deriven de clases

Su declaración es incorrecta, de ahí su confusión. C # hace permiten estructuras que se derivan de las clases. Todas las estructuras derivan de la misma clase, System.ValueType, que deriva de System.Object. Y todas las enumeraciones se derivan de System.Enum.

ACTUALIZACIÓN: Ha habido cierta confusión en algunos comentarios (ahora eliminados), lo que merece una aclaración. Haré algunas preguntas adicionales:

¿Las estructuras derivan de un tipo base?

Claramente que sí. Podemos ver esto leyendo la primera página de la especificación:

Todos los tipos de C #, incluidos los tipos primitivos como int y double, heredan de un único tipo de objeto raíz.

Ahora, observo que la especificación exagera el caso aquí. Los tipos de puntero no se derivan de un objeto y la relación de derivación para los tipos de interfaz y los tipos de parámetros de tipo es más compleja de lo que indica este esquema. Sin embargo, es evidente que todos los tipos de estructuras derivan de un tipo base.

¿Hay otras formas en las que sabemos que los tipos de estructuras se derivan de un tipo base?

Por supuesto. Un tipo de estructura puede anular ToString. ¿Qué es lo que prevalece si no es un método virtual de su tipo base? Por lo tanto, debe tener un tipo base. Ese tipo base es una clase.

¿Puedo derivar una estructura definida por el usuario de una clase de mi elección?

Claramente no. Esto no implica que las estructuras no se deriven de una clase . Las estructuras derivan de una clase y, por lo tanto, heredan los miembros hereditarios de esa clase. De hecho, se requiere que las estructuras se deriven de una clase específica: se requieren enumeraciones para derivar de ellas Enum, se requieren estructuras para derivar de ellas ValueType. Debido a que estos son obligatorios , el lenguaje C # le prohíbe indicar la relación de derivación en el código.

¿Por qué prohibirlo?

Cuando se requiere una relación , el diseñador del lenguaje tiene opciones: (1) requerir que el usuario escriba el encantamiento requerido, (2) hacerlo opcional o (3) prohibirlo. Cada uno tiene pros y contras, y los diseñadores del lenguaje C # han elegido de manera diferente según los detalles específicos de cada uno.

Por ejemplo, se requiere que los campos const sean estáticos, pero está prohibido decir que lo son porque hacerlo es, en primer lugar, una verborrea sin sentido y, en segundo lugar, implica que hay campos const no estáticos. Pero los operadores sobrecargados deben estar marcados como estáticos, aunque el desarrollador no tiene otra opción; Es demasiado fácil para los desarrolladores creer que una sobrecarga de operador es un método de instancia de otra manera. Esto anula la preocupación de que un usuario pueda llegar a creer que lo "estático" implica que, digamos, "virtual" también es una posibilidad.

En este caso, pedirle a un usuario que diga que su estructura se deriva de ValueType parece un mero exceso de palabrería, e implica que la estructura podría derivar de otro tipo. Para eliminar estos dos problemas, C # hace que sea ilegal indicar en el código que una estructura deriva de un tipo base, aunque claramente lo hace.

De manera similar, todos los tipos de delegados derivan de MulticastDelegate, pero C # requiere que no digas eso.

Entonces, ahora hemos establecido que todas las estructuras en C # derivan de una clase .

¿Cuál es la relación entre herencia y derivación de una clase ?

Mucha gente está confundida por la relación de herencia en C #. La relación de herencia es bastante sencilla: si una estructura, clase o tipo de delegado D se deriva de una clase de tipo B, los miembros hereditarios de B también son miembros de D. Es tan simple como eso.

¿Qué significa con respecto a la herencia cuando decimos que una estructura se deriva de ValueType? Simplemente que todos los miembros heredables de ValueType también son miembros de la estructura. Así es como las estructuras obtienen su implementación de ToString, por ejemplo; se hereda de la clase base de la estructura.

¿Todos los miembros heredables? Seguramente no. ¿Los miembros privados son hereditarios?

Si. Todos los miembros privados de una clase base también son miembros del tipo derivado. Por supuesto, es ilegal llamar a esos miembros por su nombre si el sitio de la llamada no está en el dominio de accesibilidad del miembro. ¡El hecho de que tenga un miembro no significa que pueda usarlo!

Ahora continuamos con la respuesta original:


¿Cómo maneja CLR esto?

Extremadamente bien. :-)

Lo que hace que un tipo de valor sea un tipo de valor es que sus instancias se copian por valor . Lo que hace que un tipo de referencia sea un tipo de referencia es que sus instancias se copian por referencia . Parece tener alguna creencia de que la relación de herencia entre los tipos de valor y los tipos de referencia es de alguna manera especial e inusual, pero no entiendo cuál es esa creencia. La herencia no tiene nada que ver con cómo se copian las cosas.

Míralo de esta manera. Suponga que le dije los siguientes hechos:

  • Hay dos tipos de cajas, cajas rojas y cajas azules.

  • Cada caja roja está vacía.

  • Hay tres casillas azules especiales llamadas O, V y E.

  • O no está dentro de ninguna caja.

  • V está dentro de O.

  • E está dentro de V.

  • No hay otra caja azul dentro de V.

  • No hay ninguna caja azul dentro de E.

  • Cada cuadro rojo está en V o E.

  • Cada caja azul que no sea O está dentro de una caja azul.

Los cuadros azules son tipos de referencia, los cuadros rojos son tipos de valor, O es System.Object, V es System.ValueType, E es System.Enum y la relación "interior" es "deriva de".

Ese es un conjunto de reglas perfectamente coherente y sencillo que podría implementar fácilmente usted mismo, si tuviera mucho cartón y mucha paciencia. Que una caja sea roja o azul no tiene nada que ver con lo que hay dentro; en el mundo real es perfectamente posible poner una caja roja dentro de una caja azul. En CLR, es perfectamente legal crear un tipo de valor que herede de un tipo de referencia, siempre que sea System.ValueType o System.Enum.

Así que reformulemos tu pregunta:

¿Cómo se derivan los ValueTypes de Object (ReferenceType) y siguen siendo ValueTypes?

como

¿Cómo es posible que cada caja roja (tipos de valor) esté dentro (se derive de) la caja O (System.Object), que es una caja azul (un Tipo de referencia) y aún sea una caja roja (un tipo de valor)?

Cuando lo expresas así, espero que sea obvio. No hay nada que le impida poner un cuadro rojo dentro del cuadro V, que está dentro del cuadro O, que es azul. ¿Por qué habría?


UNA ACTUALIZACIÓN ADICIONAL:

La pregunta original de Joan era sobre cómo es posibleque un tipo de valor deriva de un tipo de referencia. Mi respuesta original no explicó realmente ninguno de los mecanismos que utiliza el CLR para dar cuenta del hecho de que tenemos una relación de derivación entre dos cosas que tienen representaciones completamente diferentes, es decir, si los datos a los que se hace referencia tienen un encabezado de objeto, un bloque de sincronización, si posee su propio almacenamiento para la recolección de basura, etc. Estos mecanismos son complicados, demasiado complicados de explicar en una sola respuesta. Las reglas del sistema de tipos CLR son un poco más complejas que el sabor algo simplificado que vemos en C #, donde no se hace una distinción fuerte entre las versiones en caja y sin caja de un tipo, por ejemplo. La introducción de genéricos también provocó que se agregara una gran cantidad de complejidad adicional al CLR.

Eric Lippert
fuente
8
Las construcciones del lenguaje deben ser significativas. ¿Qué significaría tener un tipo de valor arbitrario derivado de un tipo de referencia arbitrario? ¿Hay algo que pueda lograr con un esquema de este tipo que no pueda lograr también con conversiones implícitas definidas por el usuario?
Eric Lippert
2
Supongo que no. Solo pensé que podría tener algunos miembros disponibles para muchos tipos de valor que ve como un grupo, lo que podría hacer usando una clase abstracta para derivar la estructura. Supongo que podría usar conversiones implícitas, pero luego pagaría una penalización por rendimiento, ¿verdad? Si está haciendo millones de ellos.
Joan Venge
8
Ah, ya veo. Desea utilizar la herencia no como un mecanismo para modelar "es una especie de" relaciones, sino simplemente como un mecanismo para compartir código entre un grupo de tipos relacionados. Ese parece un escenario razonable, aunque personalmente trato de evitar usar la herencia simplemente como una conveniencia para compartir código.
Eric Lippert
3
Joan para definir el comportamiento una vez, puede crear una interfaz, hacer que las estructuras que desea compartir el comportamiento implementen la interfaz y luego cree un método de extensión que opere en la interfaz. Un problema potencial con este enfoque es que al llamar a los métodos de interfaz, la estructura se encuadrará primero y el valor en cuadro copiado se pasará al método de extensión. Cualquier cambio de estado se producirá en la copia del objeto, lo que puede resultar poco intuitivo para los usuarios de la API.
David Silva Smith
2
@Sipo: Ahora, para ser justos, la pregunta incluye "¿cómo maneja CLR esto?" y la respuesta hace un buen trabajo al describir cómo CLR implementa estas reglas. Pero aquí está la cuestión: ¡deberíamos esperar que el sistema que implementa un lenguaje no tenga las mismas reglas que el lenguaje! Los sistemas de implementación son necesariamente de nivel inferior, pero no confundamos las reglas de ese sistema de nivel inferior con las reglas del sistema de nivel superior construido sobre él. Claro, el sistema de tipos CLR hace una distinción entre tipos de valor en caja y sin caja, como señalé en mi respuesta. Pero C # no lo hace .
Eric Lippert
21

Pequeña corrección, C # no permite que las estructuras se deriven de forma personalizada de cualquier cosa, no solo de las clases. Todo lo que puede hacer una estructura es implementar una interfaz que es muy diferente a la derivación.

Creo que la mejor forma de responder a esto es que ValueTypees especial. Es esencialmente la clase base para todos los tipos de valor en el sistema de tipos CLR. Es difícil saber cómo responder "cómo maneja el CLR esto" porque es simplemente una regla del CLR.

JaredPar
fuente
+1 para el punto positivo de que las estructuras no se derivan de nada [excepto que se deriven implícitamente de System.ValueType].
Reed Copsey
2
Dices que ValueTypees especial, pero vale la pena mencionar explícitamente que en ValueTypesí mismo es un tipo de referencia.
LukeH
Si internamente es posible que las estructuras se deriven de una clase, ¿por qué no las exponen para todos?
Joan Venge
1
@Joan: Realmente no lo hacen. Esto es solo para que pueda convertir una estructura a un objeto, y ahí por utilidad. Pero técnicamente, en comparación con cómo se implementan las clases, el CLR maneja los tipos de valor de manera completamente diferente.
Reed Copsey
2
@JoanVenge Creo que la confusión aquí dice que las estructuras se derivan de la clase ValueType dentro de CLR. Creo que es más correcto decir que dentro de CLR, las estructuras no existen realmente, la implementación de "estructura" dentro de CLR es en realidad la clase ValueType. Entonces, no es como si una estructura heredara de ValueType en CLR.
wired_in
20

Esta es una construcción algo artificial mantenida por CLR para permitir que todos los tipos sean tratados como un System.Object.

Los tipos de valor derivan de System.Object a través de System.ValueType , que es donde ocurre el manejo especial (es decir, el CLR maneja el boxeo / unboxing, etc. para cualquier tipo derivado de ValueType).

Reed Copsey
fuente
6

Su declaración es incorrecta, de ahí su confusión. C # permite que las estructuras se deriven de las clases. Todas las estructuras derivan de la misma clase, System.ValueType

Intentemos esto:

 struct MyStruct :  System.ValueType
 {
 }

Esto ni siquiera se compilará. El compilador le recordará que "Escriba 'System.ValueType' en la lista de interfaces no es una interfaz".

Cuando descompile Int32, que es una estructura, encontrará:

public struct Int32: IComparable, IFormattable, IConvertible {}, sin mencionar que se deriva de System.ValueType. Pero en el navegador de objetos, encuentra que Int32 hereda de System.ValueType.

Entonces todo esto me lleva a creer:

Creo que la mejor manera de responder a esto es que ValueType es especial. Es esencialmente la clase base para todos los tipos de valor en el sistema de tipos CLR. Es difícil saber cómo responder "cómo el CLR maneja esto" porque es simplemente una regla del CLR.

yi.han
fuente
Las mismas estructuras de datos se utilizan en .NET para describir el contenido de los tipos de valor y los tipos de referencia, pero cuando CLR ve una definición de tipo que se define como derivada ValueType, la usa para definir dos tipos de objetos: un tipo de objeto de montón que se comporta como un tipo de referencia y un tipo de ubicación de almacenamiento que está efectivamente fuera del sistema de herencia de tipos. Debido a que esos dos tipos de cosas se usan en contextos mutuamente excluyentes, se pueden usar los mismos descriptores de tipo para ambos. En el nivel CLR, una estructura se define como clase cuyo padre es System.ValueType, pero C # ...
supercat
... prohíbe especificar que las estructuras heredan de cualquier cosa porque solo hay una cosa que pueden heredar de ( System.ValueType), y prohíbe que las clases especifiquen de qué heredan System.ValueTypeporque cualquier clase que se haya declarado de esa manera se comportaría como un tipo de valor.
supercat
3

Un tipo de valor en caja es efectivamente un tipo de referencia (camina como uno y grazna como uno, por lo que efectivamente es uno). Sugeriría que ValueType no es realmente el tipo base de tipos de valor, sino que es el tipo de referencia base al que se pueden convertir los tipos de valor cuando se convierten en objetos de tipo. Los propios tipos de valores que no están encuadrados están fuera de la jerarquía de objetos.

Super gato
fuente
1
Creo que quieres decir, "ValueType no es realmente el tipo base del valor tipos"
wired_in
1
@wired_in: Gracias. Corregido.
supercat
2

Razón fundamental

De todas las respuestas, la respuesta de @ supercat se acerca más a la respuesta real. Dado que las otras respuestas realmente no responden a la pregunta, y francamente hacen afirmaciones incorrectas (por ejemplo, que los tipos de valores heredan de cualquier cosa), decidí responder la pregunta.

 

Prólogo

Esta respuesta se basa en mi propia ingeniería inversa y la especificación CLI.

structy classson palabras clave de C #. En lo que respecta a la CLI, todos los tipos (clases, interfaces, estructuras, etc.) se definen mediante definiciones de clase.

Por ejemplo, un tipo de objeto (conocido en C # como class) se define de la siguiente manera:

.class MyClass
{
}

 

Una interfaz se define mediante una definición de clase con el interfaceatributo semántico:

.class interface MyInterface
{
}

 

¿Qué pasa con los tipos de valor?

La razón por la que las estructuras pueden heredar System.ValueTypey seguir siendo tipos de valor es porque ... no lo hacen.

Los tipos de valor son estructuras de datos simples. Los tipos de valor no heredan de nada y no pueden implementar interfaces. Los tipos de valor no son subtipos de ningún tipo y no tienen ninguna información de tipo. Dada una dirección de memoria de un tipo de valor, no es posible identificar lo que representa el tipo de valor, a diferencia de un tipo de referencia que tiene información de tipo en un campo oculto.

Si imaginamos la siguiente estructura C #:

namespace MyNamespace
{
    struct MyValueType : ICloneable
    {
        public int A;
        public int B;
        public int C;

        public object Clone()
        {
            // body omitted
        }
    }
}

La siguiente es la definición de clase IL de esa estructura:

.class MyNamespace.MyValueType extends [mscorlib]System.ValueType implements [mscorlib]System.ICloneable
{
    .field public int32 A;
    .field public int32 B;
    .field public int32 C;

    .method public final hidebysig newslot virtual instance object Clone() cil managed
    {
        // body omitted
    }
}

Entonces, ¿qué está pasando aquí? Se extiende claramente System.ValueType, que es un tipo de objeto / referencia, e implementa System.ICloneable.

La explicación es que cuando la definición de una clase se extiende, System.ValueTypeen realidad define 2 cosas: un tipo de valor y el tipo en caja correspondiente del tipo de valor. Los miembros de la definición de clase definen la representación tanto para el tipo de valor como para el tipo en caja correspondiente. No es el tipo de valor que se extiende e implementa, es el tipo en caja correspondiente el que lo hace. Las palabras clave extendsy implementssolo se aplican al tipo encuadrado.

Para aclarar, la definición de clase anterior hace 2 cosas:

  1. Define un tipo de valor con 3 campos (y un método). No hereda de nada y no implementa ninguna interfaz (los tipos de valor no pueden hacer ninguna de las dos cosas).
  2. Define un tipo de objeto (el tipo en caja) con 3 campos (e implementando un método de interfaz), heredando System.ValueTypee implementando la System.ICloneableinterfaz.

Tenga en cuenta también que cualquier extensión de definición de clase System.ValueTypetambién está intrínsecamente sellada, ya sea que la sealedpalabra clave esté especificada o no.

Dado que los tipos de valor son solo estructuras simples, no heredan, no implementan y no admiten polimorfismo, no se pueden usar con el resto del sistema de tipos. Para solucionar esto, además del tipo de valor, el CLR también define un tipo de referencia correspondiente con los mismos campos, conocido como el tipo en caja. Entonces, si bien un tipo de valor no se puede pasar a métodos que toman un object, su tipo en caja correspondiente .

 

Ahora, si tuviera que definir un método en C # como

public static void BlaBla(MyNamespace.MyValueType x),

sabes que el método tomará el tipo de valor MyNamespace.MyValueType.

Arriba, aprendimos que la definición de clase que resulta de la structpalabra clave en C # en realidad define tanto un tipo de valor como un tipo de objeto. Sin embargo, solo podemos hacer referencia al tipo de valor definido. Aunque la especificación CLI establece que la palabra clave de restricción boxedse puede utilizar para hacer referencia a una versión en caja de un tipo, esta palabra clave no existe (consulte ECMA-335, II.13.1 Tipos de valores de referencia). Pero imaginemos que lo hace por un momento.

Cuando se hace referencia a tipos en IL, se admiten un par de restricciones, entre las que se encuentran classy valuetype. Si usamos valuetype MyNamespace.MyType, estamos especificando la definición de clase de tipo de valor llamada MyNamespace.MyType. Asimismo, podemos usar class MyNamespace.MyTypepara especificar la definición de clase de tipo de objeto llamada MyNamespace.MyType. Lo que significa que en IL puede tener un tipo de valor (estructura) y un tipo de objeto (clase) con el mismo nombre y aún distinguirlos. Ahora, si la boxedpalabra clave anotada por la especificación CLI se implementó realmente, podríamos usar boxed MyNamespace.MyTypepara especificar el tipo en caja de la definición de clase de tipo de valor llamada MyNamespace.MyType.

Entonces, .method static void Print(valuetype MyNamespace.MyType test) cil managedtoma el tipo de valor definido por una definición de clase de tipo de valor llamada MyNamespace.MyType,

while .method static void Print(class MyNamespace.MyType test) cil managedtoma el tipo de objeto definido por la definición de clase de tipo de objeto nombrada MyNamespace.MyType.

de la misma forma, si boxedfuera una palabra clave, .method static void Print(boxed MyNamespace.MyType test) cil managedtomaría el tipo en caja del tipo de valor definido por una definición de clase llamada MyNamespace.MyType.

Entonces podrá crear una instancia del tipo en caja como cualquier otro tipo de objeto y pasarlo a cualquier método que tome un System.ValueType, objecto boxed MyNamespace.MyValueTypecomo argumento, y funcionaría, para todos los efectos, como cualquier otro tipo de referencia. NO es un tipo de valor, sino el tipo en caja correspondiente de un tipo de valor.

 

Resumen

Entonces, en resumen, y para responder a la pregunta:

Los tipos de valor no son tipos de referencia y no heredan de System.ValueTypeningún otro tipo, y no pueden implementar interfaces. Los correspondientes recuadros tipos que están también definidas hacen hereda de System.ValueTypey pueden implementar interfaces.

Una .classdefinición define diferentes cosas según las circunstancias.

  • Si interfacese especifica el atributo semántico, la definición de clase define una interfaz.
  • Si interfaceno se especifica el atributo semántico y la definición no se extiende System.ValueType, la definición de clase define un tipo de objeto (clase).
  • Si interfaceno se especifica el atributo semántico, y la definición se extiende System.ValueType, la definición de clase define un tipo de valor y su tipo en caja correspondiente (estructura).

Disposición de la memoria

Esta sección asume un proceso de 32 bits

Como ya se mencionó, los tipos de valor no tienen información de tipo y, por lo tanto, no es posible identificar qué representa un tipo de valor desde su ubicación de memoria. Una estructura describe un tipo de datos simple y contiene solo los campos que define:

public struct MyStruct
{
    public int A;
    public short B;
    public int C;
}

Si imaginamos que se asignó una instancia de MyStruct en la dirección 0x1000, este es el diseño de la memoria:

0x1000: int A;
0x1004: short B;
0x1006: 2 byte padding
0x1008: int C;

Las estructuras tienen por defecto el diseño secuencial. Los campos están alineados en límites de su propio tamaño. Se agrega relleno para satisfacer esto.

 

Si definimos una clase exactamente de la misma manera, como:

public class MyClass
{
    public int A;
    public short B;
    public int C;
}

Imaginando la misma dirección, el diseño de la memoria es el siguiente:

0x1000: Pointer to object header
0x1004: int A;
0x1008: int C;
0x100C: short B;
0x100E: 2 byte padding
0x1010: 4 bytes extra

Las clases tienen por defecto el diseño automático, y el compilador JIT las organizará en el orden más óptimo. Los campos están alineados en límites de su propio tamaño. Se agrega relleno para satisfacer esto. No estoy seguro de por qué, pero cada clase siempre tiene 4 bytes adicionales al final.

El desplazamiento 0 contiene la dirección del encabezado del objeto, que contiene información de tipo, la tabla de método virtual, etc. Esto permite que el tiempo de ejecución identifique lo que representan los datos en una dirección, a diferencia de los tipos de valor.

Por tanto, los tipos de valor no admiten herencia, interfaces ni polimorfismo.

Métodos

Los tipos de valor no tienen tablas de métodos virtuales y, por lo tanto, no admiten polimorfismo. Sin embargo , su tipo en caja correspondiente sí lo hace .

Cuando tiene una instancia de una estructura e intenta llamar a un método virtual como se ToString()define en System.Object, el tiempo de ejecución tiene que enmarcar la estructura.

MyStruct myStruct = new MyStruct();
Console.WriteLine(myStruct.ToString()); // ToString() call causes boxing of MyStruct.

Sin embargo, si la estructura se anula ToString(), la llamada se vinculará estáticamente y el tiempo de ejecución llamará MyStruct.ToString()sin boxing y sin buscar en ninguna tabla de métodos virtuales (las estructuras no tienen ninguna). Por esta razón, también puede integrar la ToString()llamada.

Si la estructura se anula ToString()y está encuadrada, la llamada se resolverá utilizando la tabla de métodos virtuales.

System.ValueType myStruct = new MyStruct(); // Creates a new instance of the boxed type of MyStruct.
Console.WriteLine(myStruct.ToString()); // ToString() is now called through the virtual method table.

Sin embargo, recuerde que ToString()está definido en la estructura y, por lo tanto, opera sobre el valor de la estructura, por lo que espera un tipo de valor. El tipo en caja, como cualquier otra clase, tiene un encabezado de objeto. Si el ToString()método definido en la estructura fuera llamado directamente con el tipo en caja en el thispuntero, al intentar acceder al campo Aen MyStruct, accedería al desplazamiento 0, que en el tipo en caja sería el puntero del encabezado del objeto. Por lo tanto, el tipo en caja tiene un método oculto que reemplaza ToString(). Este método oculto desempaqueta (solo cálculo de direcciones, como la unboxinstrucción IL) el tipo en caja y luego llama estáticamente al ToString()definido en la estructura.

Asimismo, el tipo en caja tiene un método oculto para cada método de interfaz implementado que hace el mismo unboxing y luego llama estáticamente al método definido en la estructura.

 

Especificación CLI

Boxeo

I.8.2.4 Para cada tipo de valor, el CTS define un tipo de referencia correspondiente denominado tipo en caja. Lo contrario no es cierto: en general, los tipos de referencia no tienen un tipo de valor correspondiente. La representación de un valor de un tipo en caja (un valor en caja) es una ubicación donde se puede almacenar un valor del tipo de valor. Un tipo en caja es un tipo de objeto y un valor en caja es un objeto.

Definición de tipos de valor

I.8.9.7 No todos los tipos definidos por una definición de clase son tipos de objeto (véase §I.8.2.3); en particular, los tipos de valor no son tipos de objetos, pero se definen mediante una definición de clase. Una definición de clase para un tipo de valor define tanto el tipo de valor (sin caja) como el tipo de caja asociado (ver §I.8.2.4). Los miembros de la definición de clase definen la representación de ambos.

II.10.1.3 Los atributos semánticos de tipo especifican si se debe definir una interfaz, clase o tipo de valor. El atributo de interfaz especifica una interfaz. Si este atributo no está presente y la definición amplía (directa o indirectamente) System.ValueType, y la definición no es para System.Enum, se definirá un tipo de valor (§II.13). De lo contrario, se definirá una clase (§II.11).

Los tipos de valor no heredan

I.8.9.10 En su forma sin caja, los tipos de valor no heredan de ningún tipo. Los tipos de valor encuadrados heredarán directamente de System.ValueType a menos que sean enumeraciones, en cuyo caso, heredarán de System.Enum. Los tipos de valor en caja deben estar sellados.

II.13 Los tipos de valor sin caja no se consideran subtipos de otro tipo y no es válido utilizar la instrucción isinst (ver Partición III) en tipos de valor sin caja. Sin embargo, la instrucción isinst se puede utilizar para tipos de valores en caja.

I.8.9.10 Un tipo de valor no se hereda; más bien, el tipo base especificado en la definición de clase define el tipo base del tipo en caja.

Los tipos de valor no implementan interfaces

I.8.9.7 Los tipos de valor no admiten contratos de interfaz, pero sí los tipos encuadrados asociados.

II.13 Los tipos de valor implementarán cero o más interfaces, pero esto solo tiene significado en su forma encuadrada (§II.13.3).

I.8.2.4 Las interfaces y la herencia se definen únicamente en tipos de referencia. Por lo tanto, mientras que una definición de tipo de valor (§I.8.9.7) puede especificar ambas interfaces que serán implementadas por el tipo de valor y la clase (System.ValueType o System.Enum) de la que hereda, estos se aplican solo a valores en caja. .

La palabra clave en caja inexistente

II.13.1 Se hará referencia a la forma sin caja de un tipo de valor utilizando la palabra clave valuetype seguida de una referencia de tipo. Se hará referencia a la forma encuadrada de un tipo de valor mediante la palabra clave encuadrada seguida de una referencia de tipo.

Nota: La especificación es incorrecta aquí, no hay una boxedpalabra clave.

Epílogo

Creo que parte de la confusión de cómo los tipos de valor parecen heredar, se debe al hecho de que C # usa la sintaxis de conversión para realizar boxeo y unboxing, lo que hace que parezca que estás realizando casts, lo cual no es realmente el caso (aunque, el CLR lanzará una InvalidCastException si intenta desempaquetar el tipo incorrecto). (object)myStructen C # crea una nueva instancia del tipo en caja del tipo de valor; no realiza ningún molde. Asimismo, (MyStruct)objen C # desempaqueta un tipo en caja, copiando la parte del valor; no realiza ningún molde.

MulleDK19
fuente
1
¡Finalmente, una respuesta que describe claramente cómo funciona! Esta merece ser la respuesta aceptada. ¡Buen trabajo!
Just Shadow