initialize - public struct c#
¿Por qué se sellan los tipos de valor de.NET? (4)
No es posible heredar de una estructura C #. No es obvio para mí por qué esto es:
- Claramente, no puede tener un tipo de referencia que hereda de un tipo de valor; esto no funcionaria
- No parece razonable heredar de uno los tipos primitivos (Int32, Double, Char, etc.)
- Tendrá que poder llamar a métodos (no virtuales) en la base utilizando una instancia derivada. Podrías lanzar desde una estructura derivada a la base, ya que se superpondrían a la misma memoria. Supongo que lanzar desde una base a derivada no funcionaría, ya que no se podía saber el tipo de la estructura derivada en tiempo de ejecución.
- Veo que no puede implementar métodos virtuales en su jerarquía de clases, ya que los tipos de valores no pueden tener miembros virtuales
Me pregunto si esta es una limitación técnica en el CLR, o algo que el compilador de C # te impide hacer.
Editar: los tipos de valores no pueden tener métodos virtuales, y me doy cuenta de que esta limitación excluye la mayoría de los escenarios en los que desearía usar la herencia. Sin embargo, eso aún deja herencia como agregación. Imagine una estructura Shape
con un campo Colour
: puedo escribir código que acepte cualquier estructura derivada de Shape
, y acceder a su campo Colour
, incluso si nunca puedo escribir un método Shape.Draw
virtual.
Puedo pensar en un escenario que se rompería por tipos de valores no sellados. Se supone que los tipos de valor implementan Equals
y GetHashCode
correctamente; aunque estos dos métodos en System.Object
son virtuales, se llaman de forma no virtual en los tipos de valor. Incluso si los tipos de valor no fueron sellados, alguien que escribe una estructura derivada de otro no podría escribir su propia implementación de estos dos métodos y esperar que se los llame correctamente.
Debo señalar que no estoy sugiriendo que debería ser capaz de heredar las estructuras en mi propio código. Sin embargo, lo que estoy tratando de hacer es adivinar por qué este tipo de código en particular está prohibido por .NET.
Edición 2: Acabo de ver esta pregunta muy similar , cuya respuesta es efectivamente "porque las matrices de tipos de valores no funcionarían".
De ECMA 335: los tipos de valor serán sellados para evitar lidiar con las complicaciones del corte de valores. Las reglas más restrictivas especificadas aquí permiten una implementación más eficiente sin comprometer seriamente la funcionalidad.
No sé lo que significa ''valor de corte'', pero supongo que están sellados para permitir la implementación eficiente de la CLR.
La razón es que la mayoría de las técnicas de herencia se relacionan con el polimorfismo en tiempo de ejecución (funciones virtuales) y no funcionan en tipos de valores: para que el polimorfismo en tiempo de ejecución tenga algún significado, los objetos deben tratarse como referencias; esto tampoco es específico para .NET , es simplemente un detalle técnico de cómo se implementan las funciones virtuales.
Los tipos de valores forman una excepción a la regla de .NET, precisamente para permitir objetos livianos que no requieren direccionamiento indirecto a través de referencias. Así que el polimorfismo de tiempo de ejecución no funciona para ellos y la mayoría de los aspectos de la herencia pierden sentido.
(Hay una excepción: un objeto de tipo de valor puede estar encuadrado, lo que permite llamar a los métodos virtuales heredados de System.Object
).
Para abordar uno de tus puntos:
- Podrías lanzar desde una estructura derivada a la base, ya que se superpondrían a la misma memoria.
No, esto no sería posible; al lanzar un tipo de valor, se copiaría su valor. No estamos tratando con referencias aquí, así que no hay superposición en la memoria. Por lo tanto, convertir un tipo de valor a su tipo base no tiene sentido (nuevamente, a menos que hablemos de la conversión a un object
que en realidad lleva a cabo el boxeo debajo del capó, y también opera en una copia del valor).
Todavía no está claro? Veamos un ejemplo.
Digamos que tenemos la struct Shape
hipotética struct Shape
y, heredando de ella, la struct Circle
. Shape
define un método de Draw
virtual (que acepta un objeto de Graphics
). Ahora, digamos que queremos dibujar una forma en un lienzo. Esto, por supuesto, funciona perfectamente bien:
var circle = new Circle(new Point(10, 10), 20);
circle.Draw(e.Graphics); // e.Graphics = graphics object of our form.
- Pero aquí en realidad no usamos la herencia en absoluto. Para hacer uso de la herencia, imagine en su lugar el siguiente método auxiliar DrawObject
:
void DrawObject(Shape shape, Graphics g) {
// Do some preparation on g.
shape.Draw(g);
}
Y lo llamamos en otro lugar con un Circle
:
var circle = new Circle(new Point(10, 10), 20);
DrawObject(circle, e.Graphics);
- Y, ka-blam - este código no dibuja un círculo. ¿Por qué? Porque cuando pasamos el círculo al método DrawObject
, hacemos dos cosas:
- Nosotros lo copiamos
- Lo cortamos , es decir, el objeto de
shape
objeto ya no es unCircle
, ni el original ni la copia. En cambio, su porción deCircle
fue "rebanada" durante la copia y solo queda la parte deShape
.shape.Draw
ahora llama al métodoDraw
deShape
, no deCircle
.
En C ++, en realidad puedes causar este comportamiento. Por esa razón, OOP en C ++ solo funciona en punteros y referencias, no en tipos de valores directamente. Y por esa misma razón, .NET solo permite la herencia de tipos de referencia porque de todos modos no podría usarlo para tipos de valores.
Tenga en cuenta que el código anterior funciona en .NET si Shape
es una interfaz. En otras palabras, un tipo de referencia . Ahora la situación es diferente: su objeto circle
aún se copiará, pero también se incluirá en una referencia.
Ahora, .NET teóricamente te podría permitir heredar una struct
de una class
. Entonces, el código anterior funcionaría tan bien como si Shape
fuera una interfaz. Pero entonces, la ventaja de tener una struct
en primer lugar se desvanece: a todos los efectos (excepto las variables locales que nunca pasan a otro método, por lo tanto, no tienen utilidad de herencia), su struct
se comportará como un tipo de referencia inmutable de un tipo de valor
Porque cada instancia de tipo de valor tiene un tamaño diferente y se almacena en la pila. Entonces, si escribe "Base = Derivada" donde "Base" y "Derivada" son tipos de valor, se dañará la pila.
Puede tener alguna forma limitada de herencia usando parámetros de tipo genéricos en tipos de valores.