Sé que las estructuras en .NET no admiten la herencia, pero no está exactamente claro por qué están limitadas de esta manera.
¿Qué razón técnica impide que las estructuras hereden de otras estructuras?
.net
inheritance
struct
Julieta
fuente
fuente
Respuestas:
La razón por la que los tipos de valor no pueden admitir la herencia se debe a las matrices.
El problema es que, por razones de rendimiento y GC, las matrices de tipos de valores se almacenan "en línea". Por ejemplo, dado que
new FooType[10] {...}
siFooType
es un tipo de referencia, se crearán 11 objetos en el montón administrado (uno para la matriz y 10 para cada instancia de tipo). Si, enFooType
cambio, es un tipo de valor, solo se creará una instancia en el montón administrado, para la matriz misma (ya que cada valor de la matriz se almacenará "en línea" con la matriz).Ahora, supongamos que tenemos herencia con tipos de valor. Cuando se combina con el comportamiento anterior de "almacenamiento en línea" de las matrices, suceden cosas malas, como se puede ver en C ++ .
Considere este código pseudo-C #:
Según las reglas de conversión normales, a
Derived[]
es convertible a aBase[]
(para bien o para mal), por lo que si s / struct / class / g para el ejemplo anterior, se compilará y ejecutará como se esperaba, sin problemas. Pero siBase
yDerived
son tipos de valores, y las matrices almacenan valores en línea, entonces tenemos un problema.Tenemos un problema porque
Square()
no sabe nadaDerived
, solo usará aritmética de puntero para acceder a cada elemento de la matriz, incrementándolo en una cantidad constante (sizeof(A)
). La asamblea sería vagamente como:(Sí, es un ensamblaje abominable, pero el punto es que incrementaremos a través de la matriz en constantes de tiempo de compilación conocidas, sin ningún conocimiento de que se esté utilizando un tipo derivado).
Entonces, si esto realmente sucediera, tendríamos problemas de corrupción de memoria. En concreto, dentro
Square()
,values[1].A*=2
sería realmente estar modificandovalues[0].B
!Intenta depurar ESO !
fuente
Imagine estructuras compatibles con la herencia. Luego declarando:
significaría que las variables de estructura no tienen un tamaño fijo, y es por eso que tenemos tipos de referencia.
Aún mejor, considere esto:
fuente
Foo
herencia de estructuraBar
no debe permitirFoo
que se asigne a aBar
, pero declarar una estructura de esa manera podría permitir un par de efectos útiles: (1) Crear un miembro de tipo especialmente nombradoBar
como el primer elemento enFoo
eFoo
incluir nombres de los miembros que alias a aquellos miembros enBar
, lo que permite código que habían usadoBar
para adaptarse al uso de unaFoo
vez, sin tener que reemplazar todas las referencias athing.BarMember
conthing.theBar.BarMember
, y retener la capacidad de leer y escribir todosBar
's campos como grupo; ...Las estructuras no usan referencias (a menos que estén encuadradas, pero debe tratar de evitar eso), por lo tanto, el polimorfismo no es significativo ya que no hay indirección a través de un puntero de referencia. Los objetos normalmente viven en el montón y se hace referencia a ellos mediante punteros de referencia, pero las estructuras se asignan en la pila (a menos que estén encuadradas) o se asignan "dentro" de la memoria ocupada por un tipo de referencia en el montón.
fuente
Foo
que tenga un campo de tipo estructuraBar
capaz de considerar aBar
los miembros como propios, de modo que unaPoint3d
clase podría, por ejemplo, encapsular aPoint2d xy
pero referirse aX
ese campo comoxy.X
oX
.Esto es lo que dicen los documentos :
Básicamente, se supone que contienen datos simples y, por lo tanto, no tienen "características adicionales" como la herencia. Probablemente sería técnicamente posible para ellos admitir algún tipo limitado de herencia (no polimorfismo, debido a que están en la pila), pero creo que también es una opción de diseño no admitir la herencia (como muchas otras cosas en .NET los idiomas son)
Por otro lado, estoy de acuerdo con los beneficios de la herencia, y creo que todos hemos llegado al punto en que queremos
struct
que heredemos de otro, y nos damos cuenta de que no es posible. Pero en ese punto, la estructura de datos es probablemente tan avanzada que de todos modos debería ser una clase.fuente
Point3D
de aPoint2D
; no sería capaz de usar un enPoint3D
lugar de unPoint2D
, pero noPoint3D
class
sobrestruct
su caso.La clase como herencia no es posible, ya que una estructura se coloca directamente en la pila. Una estructura heredada sería más grande que su padre, pero el JIT no lo sabe e intenta poner demasiado en menos espacio. Suena un poco confuso, escribamos un ejemplo:
Si esto fuera posible, se bloquearía en el siguiente fragmento:
Se asigna espacio para el tamaño de A, no para el tamaño de B.
fuente
Hay un punto que me gustaría corregir. Aunque la razón por la que las estructuras no pueden heredarse es porque viven en la pila es la correcta, es al mismo tiempo una explicación medio correcta. Las estructuras, como cualquier otro tipo de valor, pueden vivir en la pila. Debido a que dependerá de dónde se declare la variable, vivirán en la pila o en el montón . Esto será cuando sean variables locales o campos de instancia respectivamente.
Al decir eso, Cecil tiene un nombre lo clavó correctamente.
Me gustaría enfatizar esto, los tipos de valor pueden vivir en la pila. Esto no significa que siempre lo hagan. Las variables locales, incluidos los parámetros del método, lo harán. Todos los demás no lo harán. Sin embargo, sigue siendo la razón por la que no se pueden heredar. :-)
fuente
Las estructuras se asignan en la pila. Esto significa que la semántica del valor es bastante gratuita, y acceder a los miembros de la estructura es muy barato. Esto no previene el polimorfismo.
Puede hacer que cada estructura comience con un puntero a su tabla de funciones virtuales. Esto sería un problema de rendimiento (cada estructura tendría al menos el tamaño de un puntero), pero es factible. Esto permitiría funciones virtuales.
¿Qué hay de agregar campos?
Bueno, cuando asigna una estructura en la pila, asigna una cierta cantidad de espacio. El espacio requerido se determina en tiempo de compilación (ya sea antes de tiempo o cuando JITting). Si agrega campos y luego los asigna a un tipo base:
Esto sobrescribirá alguna parte desconocida de la pila.
La alternativa es que el tiempo de ejecución evite esto escribiendo solo bytes sizeof (A) en cualquier variable A.
¿Qué sucede si B anula un método en A y hace referencia a su campo Integer2? O el tiempo de ejecución arroja una excepción MemberAccessException o el método accede a algunos datos aleatorios en la pila. Ninguno de estos es permisible.
Es perfectamente seguro tener herencia de estructura, siempre y cuando no use estructuras polimórficas, o mientras no agregue campos al heredar. Pero estos no son terriblemente útiles.
fuente
Esta parece una pregunta muy frecuente. Tengo ganas de agregar que los tipos de valores se almacenan "en el lugar" donde declaras la variable; aparte de los detalles de implementación, esto significa que no hay un encabezado de objeto que diga algo sobre el objeto, solo la variable sabe qué tipo de datos reside allí.
fuente
Las estructuras admiten interfaces, por lo que puede hacer algunas cosas polimórficas de esa manera.
fuente
IL es un lenguaje basado en pila, por lo que llamar a un método con un argumento es algo así:
Cuando se ejecuta el método, saca algunos bytes de la pila para obtener su argumento. Sabe exactamente cuántos bytes aparecer porque el argumento es un puntero de tipo de referencia (siempre 4 bytes en 32 bits) o es un tipo de valor para el que el tamaño siempre se conoce exactamente.
Si se trata de un puntero de tipo de referencia, el método busca el objeto en el montón y obtiene su identificador de tipo, que apunta a una tabla de métodos que maneja ese método en particular para ese tipo exacto. Si se trata de un tipo de valor, no es necesario buscar una tabla de métodos porque los tipos de valores no admiten la herencia, por lo que solo hay una combinación posible de método / tipo.
Si los tipos de valor admitían la herencia, habría una sobrecarga adicional en que el tipo particular de la estructura tendría que colocarse en la pila, así como su valor, lo que significaría algún tipo de búsqueda en la tabla de métodos para la instancia concreta particular del tipo. Esto eliminaría las ventajas de velocidad y eficiencia de los tipos de valor.
fuente