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

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

¿Cómo maneja esto el CLR?

C # no permite que las estructuras deriven de las clases

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

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

¿Las estructuras derivan de un tipo base?

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

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

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

¿Hay otras formas en que sepamos que los tipos de estructuras derivan de un tipo base?

Por supuesto. Un tipo de estructura puede anular ToString . ¿Qué es lo que prevalece, sino un método virtual de su tipo base? Por lo tanto, debe tener un tipo de 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 derivan de una clase . Las estructuras derivan de una clase y, por lo tanto, heredan los miembros heredables de esa clase. De hecho, se requieren estructuras para derivar de una clase específica: se requiere que los enum deriven de Enum , se requieren estructuras para derivar de ValueType . Debido a que son necesarios , el lenguaje C # le prohíbe indicar la relación de derivación en el código.

¿Por qué no?

Cuando se requiere una relación, el diseñador del lenguaje tiene opciones: (1) requiere que el usuario escriba el conjuro requerido, (2) lo haga opcional, o (3) lo prohíba. Cada uno tiene ventajas y desventajas, y los diseñadores del lenguaje C # han elegido de forma diferente dependiendo de los detalles específicos de cada uno.

Por ejemplo, los campos const deben ser estáticos, pero está prohibido decir que lo son porque hacerlo es primero verborrea sin sentido, y segundo, implica que hay campos const no estáticos. Pero los operadores sobrecargados deben marcarse como estáticos, aunque el desarrollador no tenga otra opción; es demasiado fácil para los desarrolladores creer que una sobrecarga del operador es un método de instancia en caso contrario. Esto anula la preocupación de que un usuario pueda llegar a creer que lo “estático” implica que, por ejemplo, “virtual” también es una posibilidad.

En este caso, requerir que un usuario diga que su estructura se deriva de ValueType parece una mera verborrea excesiva e implica que la estructura podría derivarse 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.

Del mismo modo, todos los tipos de delegado derivan de MulticastDelegate , pero C # requiere que usted no lo diga.

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

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

Muchas personas están confundidas por la relación de herencia en C #. La relación de herencia es bastante sencilla: si una estructura, clase o delegado tipo D deriva de una clase tipo B, los miembros hereditarios de B también son miembros de D. Es así de simple.

¿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 hereditarios? Seguramente no. ¿Los miembros privados son heredables?

Sí. Todos los miembros privados de una clase base también son miembros del tipo derivado. Es ilegal llamar a esos miembros por su nombre, por supuesto, si el sitio de llamadas no se encuentra en el dominio de accesibilidad del miembro. ¡Solo porque tengas un miembro no significa que puedas usarlo!

Ahora continuamos con la respuesta original:


¿Cómo maneja esto el CLR?

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 que crees que la relación de herencia entre los tipos de valores 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 la forma en que se copian las cosas.

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

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

  • Cada caja roja está vacía.

  • Hay tres cajas 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 una caja azul dentro E.

  • Cada caja roja está en V o E.

  • Cada caja azul que no sea O se encuentra dentro de una caja azul.

Los cuadros azules son tipos de referencia, los cuadros rojos son tipos de valores, O es System.Object, V es System.ValueType, E es System.Enum, y la relación “inside” es “deriva de”.

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

Entonces, reformulemos su pregunta:

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

como

¿Cómo es posible que cada cuadro rojo (tipos de valor) esté dentro de (se deriva de) el cuadro O (Objeto del sistema), que es un cuadro azul (un Tipo de referencia) y sigue siendo un cuadro rojo (un tipo de valor)?

Cuando lo expresas así, espero que sea obvio. No hay nada que te impida poner una caja roja dentro de la caja V, que está dentro de la caja O, que es azul. ¿Por qué habría?


UNA ACTUALIZACIÓN ADICIONAL:

La pregunta original de Joan fue sobre cómo es posible que un tipo de valor derive de un tipo de referencia. Mi respuesta original en realidad no explicaba ninguno de los mecanismos que utiliza el CLR para explicar el hecho de que tenemos una relación de derivación entre dos cosas que tienen representaciones completamente diferentes: a saber, si los datos referidos tienen un encabezado de objeto, un bloque de sincronización, si posee su propio almacenamiento para la recolección de basura, y así sucesivamente. Estos mecanismos son complicados, demasiado complicados de explicar en una respuesta. Las reglas del sistema de tipo CLR son bastante más complejas que el sabor algo simplificado que vemos en C #, donde no se hace una fuerte distinción entre las versiones en caja y sin caja de un tipo, por ejemplo. La introducción de generics también causó una gran complejidad adicional para ser agregada al CLR. Consulte la especificación CLI para obtener más información, prestando especial atención a las reglas para el boxeo y las llamadas virtuales restringidas.

Esta es una construcción un tanto artificial mantenida por el CLR para permitir que todos los tipos se traten como un System.Object.

Los tipos de valores se derivan de System.Object a través de System.ValueType , que es donde se produce el tratamiento especial (es decir: el CLR maneja el boxeo / unboxing, etc. para cualquier tipo derivado de ValueType).

Corrección pequeña, C # no permite que las estructuras derivadas de cualquier cosa se personalicen, no solo las clases. Todo lo que una estructura puede hacer es implementar una interfaz que es muy diferente de la derivación.

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

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

Probemos esto:

  struct MyStruct : System.ValueType { } 

Esto ni siquiera comstackrá. El comstackdor 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, usted encuentra que Int32 hereda de System.ValueType.

Entonces todos estos me llevan a creer:

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

Un tipo de valor encuadrado es efectivamente un tipo de referencia (camina como uno y granea como uno, así que efectivamente es uno). Sugeriría que ValueType no es realmente el tipo base de tipos de valores, sino que es el tipo de referencia base a la que se pueden convertir los tipos de valores cuando se los convierte a tipo Object. Los propios tipos de valores que no están enmarcados están fuera de la jerarquía de objetos.