c# - tipos - ¿Cuándo un tipo de valor contendría un tipo de referencia?
tipos de datos por referencia en c# (3)
Entiendo que la decisión de usar un tipo de valor sobre un tipo de referencia debe basarse en la semántica, no en el desempeño. No entiendo por qué los tipos de valor pueden contener legalmente miembros de tipo de referencia. Esto es por un par de razones:
Por un lado, no deberíamos construir una estructura para requerir un constructor.
public struct MyStruct
{
public Person p;
// public Person p = new Person(); // error: cannot have instance field initializers in structs
MyStruct(Person p)
{
p = new Person();
}
}
Segundo, debido a la semántica del tipo de valor:
MyStruct someVariable;
someVariable.p.Age = 2; // NullReferenceException
El compilador no me permite inicializar Person
en la declaración. Tengo que cambiar esto al constructor, confiar en la persona que llama o esperar una NullReferenceException
. Ninguna de estas situaciones es ideal.
¿Tiene .NET Framework algún ejemplo de tipos de referencia dentro de los tipos de valor? ¿Cuándo deberíamos hacer esto (si alguna vez)?
Hay dos escenarios útiles principales para una estructura que contiene un campo de tipo de clase:
- La estructura contiene una referencia posiblemente mutable a un objeto inmutable (siendo `String` el más común con diferencia). Una referencia a un objeto inmutable se comportará como una cruz entre un tipo de valor anulable y un tipo de valor normal; no tiene las propiedades "Valor" y "Valor de Has" de la anterior, pero tendrá un valor nulo como posible (y predeterminado). Tenga en cuenta que si se accede al campo a través de una propiedad, esa propiedad puede devolver un valor predeterminado no nulo cuando el campo es nulo, pero no debe modificar el campo en sí.
- La estructura contiene una referencia "inmutable" a un objeto posiblemente mutable y sirve para envolver el objeto o su contenido. `List.Enumerator` es probablemente la estructura más común que usa este patrón. Tener campos struct pretender ser inmutables es algo así como una construcción poco fiable (*), pero en algunos contextos puede funcionar bastante bien. En la mayoría de los casos donde se aplica este patrón, el comportamiento de una estructura será esencialmente similar al de una clase, excepto que el rendimiento será mejor (**).
(*) La instrucción structVar = new structType(whatever);
creará una nueva instancia de structType
, la pasará al constructor y luego structVar
copiando todos los campos públicos y privados de esa nueva instancia en structVar
; Una vez hecho esto, la nueva instancia será descartada. En consecuencia, todos los campos de estructura son mutables, incluso si "pretenden" ser de otra manera; fingir que son inmutables puede ser poco fiable a menos que uno sepa que la forma en que structVar = new structType(whatever);
Se implementa realmente nunca planteará un problema.
(**) Las estructuras se desempeñarán mejor en algunas circunstancias; Las clases se desempeñarán mejor en los demás. En general, las llamadas estructuras "inmutables" se eligen sobre clases en situaciones en las que se espera que tengan un mejor desempeño, y donde los casos de esquina donde su semántica podría diferir de los de las clases no se espera que presenten problemas.
A algunas personas les gusta fingir que las estructuras son como las clases, pero más eficientes, y no les gusta usar las estructuras de manera que aprovechen el hecho de que no son clases. Estas personas probablemente solo estarían inclinadas a usar el escenario (2) anterior. El escenario # 1 puede ser muy útil con estructuras mutables, especialmente con tipos como String
que se comportan esencialmente como valores.
Las instancias de un tipo de valor nunca contienen instancias de un tipo de referencia. El objeto de tipo de referencia está en algún lugar del montón administrado, y el objeto de tipo de valor puede contener una referencia al objeto. Tal referencia tiene un tamaño fijo. Es perfectamente común hacer esto, por ejemplo, cada vez que usa una cadena dentro de una estructura.
Pero sí, no puede garantizar la inicialización de un campo de tipo de referencia en una struct
porque no puede definir un constructor sin parámetros (ni puede garantizar que se llame nunca, si lo define en un idioma que no sea C #).
Usted dice que debe "no construir una struct
para requerir un constructor". Yo digo lo contrario. Dado que los tipos de valor casi siempre deben ser inmutables, debe usar un constructor (muy posiblemente a través de una fábrica para un constructor privado). De lo contrario nunca tendrá ningún contenido interesante.
Usa el constructor. El constructor está bien.
Si no desea pasar una instancia de Person
para inicializar p
, puede usar una inicialización perezosa a través de una propiedad. (Porque obviamente el campo público p
era solo para demostración, ¿verdad? ¿Verdad?)
public struct MyStruct
{
public MyStruct(Person p)
{
this.p = p;
}
private Person p;
public Person Person
{
get
{
if (p == null)
{
p = new Person(…); // see comment below about struct immutability
}
return p;
}
}
// ^ in most other cases, this would be a typical use case for Lazy<T>;
// but due to structs'' default constructor, we *always* need the null check.
}
Quería agregar a la respuesta de Marc, pero tenía mucho que decir para un comentario.
Si nos fijamos en las especificaciones de C #, dice de constructores de estructuras:
Los constructores Struct se invocan con el nuevo operador, pero eso no implica que se esté asignando memoria. En lugar de asignar dinámicamente un objeto y devolverle una referencia, un constructor de estructura simplemente devuelve el propio valor de estructura (normalmente en una ubicación temporal en la pila), y este valor se copia según sea necesario.
(Puede encontrar una copia de la especificación en
C:/Program Files (x86)/Microsoft Visual Studio 10.0/VC#/Specifications/1033
)
Por lo tanto, un constructor de estructura es inherentemente diferente de un constructor de clase.
Además de esto, se espera que las estructuras se copien por valor, y por lo tanto:
Con las estructuras, cada una de las variables tiene su propia copia de los datos, y no es posible que las operaciones en una afecten a la otra.
Cada vez que he visto un tipo de referencia en una estructura, ha sido una cadena. Esto funciona porque las cuerdas son inmutables. Supongo que su objeto Person
no es inmutable y puede introducir errores muy extraños y graves debido a la divergencia del comportamiento esperado de una estructura.
Dicho esto, los errores que está viendo con el constructor de su estructura pueden ser que tiene un campo público p
con el mismo nombre que su parámetro p
y que no se refiere a la estructura de la estructura como this.p
, o que está falta la palabra clave struct
.