c# - significado - Tipos de Nullable de boxeo/desempaque: ¿Por qué esta implementación?
qué es el tipo nullable (5)
Creo que tiene sentido encerrar un valor nulo en una referencia nula. Tener un valor en caja que dice "Sé que sería un Int32
si tuviera un valor, pero no" no me parece intuitivo. Es mejor pasar de la versión de tipo de valor de "no un valor" (un valor con HasValue
como falso) a la versión de tipo de referencia de "no un valor" (una referencia nula).
Creo que este cambio se hizo en la retroalimentación de la comunidad, por cierto.
Esto también permite un uso interesante de incluso para tipos de valor:
object mightBeADouble = GetMyValue();
double? unboxed = mightBeADouble as double?;
if (unboxed != null)
{
...
}
Esto es más coherente con la forma en que se manejan las "conversiones inciertas" con tipos de referencia, que con la anterior:
object mightBeADouble = GetMyValue();
if (mightBeADouble is double)
{
double unboxed = (double) mightBeADouble;
...
}
(También puede funcionar mejor, ya que solo hay una única verificación de tipo de tiempo de ejecución).
Extraiga de CLR a través de C # en los tipos de valor de Boxing / Unboxing ...
En el boxeo: si la instancia anulable no es nula , el CLR toma el valor de la instancia anulable y la encuadra. En otras palabras, un <Nullable <Int32> con un valor de 5 está encuadrado en un box -Int32 con un valor de 5.
En Unboxing: Unboxing es simplemente el acto de obtener una referencia a la parte sin caja de un objeto en caja. El problema es que un tipo de valor encuadrado no puede simplemente desempaquetarse en una versión anulable de ese tipo de valor porque el valor encuadrado no tiene el campo hasValue booleano en él. Por lo tanto, al desempaquetar un tipo de valor en una versión con posibilidad de nulos , el CLR debe asignar un objeto Nullable <T> , inicializar el campo hasValue en verdadero y establecer el campo del valor en el mismo valor que se encuentra en el tipo de valor en el recuadro. Esto afecta el rendimiento de su aplicación (asignación de memoria durante la desinstalación).
¿Por qué el equipo CLR sufrió tantos problemas con los tipos de Nullable? ¿Por qué no fue simplemente encajonado en un <Int32> de Nullable en primer lugar?
Recuerdo que este comportamiento fue un cambio de último minuto. En las primeras Nullable<T>
beta de .NET 2.0, Nullable<T>
era un tipo de valor "normal". Boxeo un valor null
int?
lo convirtió en un int?
en caja int?
con una bandera booleana. Creo que la razón por la que decidieron elegir el enfoque actual es la consistencia. Decir:
int? test = null;
object obj = test;
if (test != null)
Console.WriteLine("test is not null");
if (obj != null)
Console.WriteLine("obj is not null");
En el enfoque anterior (casilla null
-> Nullable<T>
caja Nullable<T>
), no obtendría "la prueba no es nula", pero obtendría "el objeto no es nula", lo cual es extraño.
Además, si hubieran recuadrado un valor anulable en un boxed-Nullable<T>
:
int? val = 42;
object obj = val;
if (obj != null) {
// Our object is not null, so intuitively it''s an `int` value:
int x = (int)obj; // ...but this would have failed.
}
Además de eso, creo que el comportamiento actual tiene mucho sentido para escenarios como valores de base de datos que admiten nulos (piense en SQL-CLR ...)
Aclaración:
El objetivo de proporcionar tipos anulables es facilitar el manejo de las variables que no tienen un valor significativo. No querían proporcionar dos tipos distintos, no relacionados. Un int?
Debería comportarse más o menos como un simple int
. Es por eso que C # proporciona operadores elevados.
Por lo tanto, al desempaquetar un tipo de valor en una versión con
Nullable<T>
, el CLR debe asignar unNullable<T>
que admite nulos, inicializar el campo hasValue en verdadero y establecer el campo del valor en el mismo valor que se encuentra en el tipo de valor en el cuadro. Esto afecta el rendimiento de su aplicación (asignación de memoria durante la desinstalación).
Esto no es verdad. El CLR tendría que asignar memoria en la pila para mantener la variable, ya sea que sea anulable o no. No hay un problema de rendimiento para asignar espacio para una variable booleana adicional.
Supongo que eso es básicamente lo que hace. La descripción incluida incluye su sugerencia (es decir, el boxeo en un Nullable<T>
).
El extra es que establece el campo hasValue
después del boxeo.
Una cosa que obtiene a través de este comportamiento es que la versión en caja implementa todas las interfaces compatibles con el tipo subyacente. (El objetivo es hacer que Nullable<int>
el mismo Nullable<int>
que int
para todos los propósitos prácticos). El boxeo en una boxed-Nullable<int>
lugar de en una boxed-int
evitaría este comportamiento.
Desde la página de MSDN,
double? d = 44.4;
object iBoxed = d;
// Access IConvertible interface implemented by double.
IConvertible ic = (IConvertible)iBoxed;
int i = ic.ToInt32(null);
string str = ic.ToString();
Además, obtener el int desde una versión en caja de un Nullable<int>
es sencillo. Por lo general, no se puede desempaquetar en un tipo que no sea el tipo src original.
float f = 1.5f;
object boxed_float = f;
int int_value = (int) boxed_float; // will blow up. Cannot unbox a float to an int, you *must* unbox to a float first.
float? nullableFloat = 1.4f;
boxed_float = nullableFloat;
float fValue = (float) boxed_float; // can unbox a float? to a float Console.WriteLine(fValue);
Aquí no tiene que saber si la versión original era una versión int o una versión de Nullable. (+ también obtienes algo de rendimiento; ahorra espacio de almacenamiento del hasValue
boolean también en el objeto en caja)
Yo diría que la razón del comportamiento se deriva del comportamiento de Object.Equals, especialmente el hecho de que si el primer objeto es nulo y el segundo no, Object.Equals devuelve false en lugar de llamar al método Equals en el segundo objeto.
Si Object.Equals hubiera llamado al método Equals en el segundo objeto en el caso de que el primer objeto fuera nulo pero el segundo no lo fuera, entonces un objeto cuyo valor nulo sea Nullable <T> podría haber devuelto True en comparación con null. Personalmente, creo que el remedio adecuado hubiera sido que la propiedad HasValue de un <T> sin Nullable no tuviera nada que ver con el concepto de una referencia nula. Con respecto a la sobrecarga involucrada con el almacenamiento de una bandera booleana en el montón, se podría haber provisto que para cada tipo de Nullable <T> habría una versión vacía en caja estática, y luego proporcionar que desempaquetar la copia vacía en caja estática proporcionaría un vacía Nullable <T>, y desempaquetar cualquier otra instancia daría lugar a una rellenada.