c# - nullables - qué es el tipo nullable
¿Cuál es el preferido: Nullable<>. HasValue o Nullable<>!=Null? (6)
Siempre usé (a) Nullable<>.HasValue
porque me gustó la semántica. Sin embargo, recientemente estuve trabajando en la base de código existente de otra persona donde usaron (b) Nullable<> != null
exclusivamente en su lugar. ¿Hay alguna razón para usar una sobre la otra, o es simplemente una preferencia?
(una)
int? a;
if (a.HasValue)
...
(segundo)
int? b;
if (b != null)
...
El compilador reemplaza las comparaciones nulas con una llamada a HasValue
, por lo que no hay una diferencia real. Simplemente haga lo que sea más legible / tenga más sentido para usted y sus colegas.
En VB.Net. NO use "No es nada" cuando puede usar ".HasValue". Acabo de resolver una "Operación podría desestabilizar el tiempo de ejecución" Error de confianza del medio al reemplazar "No es nada" con ".HasValue" En un punto. Realmente no entiendo por qué, pero algo está sucediendo de manera diferente en el compilador. Supongo que "! = Null" en C # puede tener el mismo problema.
Hice una investigación sobre esto utilizando diferentes métodos para asignar valores a un int. Esto es lo que sucedió cuando hice varias cosas. Debería aclarar lo que está pasando. Tenga en cuenta: ¿ Nullable<something>
o la abreviatura something?
es una estructura para la cual el compilador parece estar haciendo un gran trabajo para permitirnos usar con null como si fuera una clase.
Como verá a continuación, SomeNullable == null
y SomeNullable.HasValue
siempre devolverá un verdadero o falso esperado. Aunque no se muestra a continuación, SomeNullable == 3
es válido (¿suponiendo que SomeNullable sea un int?
).
Mientras SomeNullable.Value
nos da un error de tiempo de ejecución si asignamos null
a SomeNullable
. De hecho, este es el único caso en el que las nullables podrían causarnos un problema, gracias a una combinación de operadores sobrecargados, object.Equals(obj)
sobrecargados. object.Equals(obj)
y optimización de compiladores y negocios de monos.
Aquí hay una descripción de algunos códigos que ejecuté, y qué salida produjo en las etiquetas:
int? val = null;
lbl_Val.Text = val.ToString(); //Produced an empty string.
lbl_ValVal.Text = val.Value.ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValEqNull.Text = (val == null).ToString(); //Produced "True" (without the quotes)
lbl_ValNEqNull.Text = (val != null).ToString(); //Produced "False"
lbl_ValHasVal.Text = val.HasValue.ToString(); //Produced "False"
lbl_NValHasVal.Text = (!(val.HasValue)).ToString(); //Produced "True"
lbl_ValValEqNull.Text = (val.Value == null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValValNEqNull.Text = (val.Value != null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")
Ok, vamos a probar el siguiente método de inicialización:
int? val = new int?();
lbl_Val.Text = val.ToString(); //Produced an empty string.
lbl_ValVal.Text = val.Value.ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValEqNull.Text = (val == null).ToString(); //Produced "True" (without the quotes)
lbl_ValNEqNull.Text = (val != null).ToString(); //Produced "False"
lbl_ValHasVal.Text = val.HasValue.ToString(); //Produced "False"
lbl_NValHasVal.Text = (!(val.HasValue)).ToString(); //Produced "True"
lbl_ValValEqNull.Text = (val.Value == null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")
lbl_ValValNEqNull.Text = (val.Value != null).ToString(); //Produced a runtime error. ("Nullable object must have a value.")
Igual que antes. Tenga en cuenta que inicializar con int? val = new int?(null);
int? val = new int?(null);
, con nulo pasado al constructor, habría producido un error de tiempo de COMPILACIÓN, ya que el VALOR del objeto que acepta valores nulos NO es anulable. Es solo el objeto contenedor que puede ser nulo.
Del mismo modo, obtendríamos un error de compilación de:
int? val = new int?();
val.Value = null;
por no mencionar que val.Value
es una propiedad de solo lectura, lo que significa que no podemos usar algo como:
val.Value = 3;
pero, de nuevo, los operadores de conversión implícita sobrecargados polimorfos nos permiten hacer:
val = 3;
Sin embargo, no hay necesidad de preocuparse por la política de lo que se llama, siempre que funcione bien. :)
Prefiero (a != null)
para que la sintaxis coincida con los tipos de referencia.
Respuesta general y regla general: si tiene una opción (por ejemplo, la creación de serializadores personalizados) para procesar Nullable en una canalización diferente a la del object
, y usar sus propiedades específicas, hágalo y use las propiedades específicas de Nullable. Por lo tanto, desde el punto de vista del pensamiento HasValue
debe preferir HasValue
. El pensamiento coherente puede ayudarlo a escribir un código mejor, sin perder demasiado tiempo en detalles. Por ejemplo, el segundo método será mucho más efectivo (principalmente debido a los compiladores en línea y al boxeo, pero los números son muy expresivos):
public static bool CheckObjectImpl(object o)
{
return o != null;
}
public static bool CheckNullableImpl<T>(T? o) where T: struct
{
return o.HasValue;
}
Prueba de referencia:
BenchmarkDotNet=v0.10.5, OS=Windows 10.0.14393
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233539 Hz, Resolution=309.2587 ns, Timer=TSC
[Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
Clr : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
Core : .NET Core 4.6.25009.03, 64bit RyuJIT
Method | Job | Runtime | Mean | Error | StdDev | Min | Max | Median | Rank | Gen 0 | Allocated |
-------------- |----- |-------- |-----------:|----------:|----------:|-----------:|-----------:|-----------:|-----:|-------:|----------:|
CheckObject | Clr | Clr | 80.6416 ns | 1.1983 ns | 1.0622 ns | 79.5528 ns | 83.0417 ns | 80.1797 ns | 3 | 0.0060 | 24 B |
CheckNullable | Clr | Clr | 0.0029 ns | 0.0088 ns | 0.0082 ns | 0.0000 ns | 0.0315 ns | 0.0000 ns | 1 | - | 0 B |
CheckObject | Core | Core | 77.2614 ns | 0.5703 ns | 0.4763 ns | 76.4205 ns | 77.9400 ns | 77.3586 ns | 2 | 0.0060 | 24 B |
CheckNullable | Core | Core | 0.0007 ns | 0.0021 ns | 0.0016 ns | 0.0000 ns | 0.0054 ns | 0.0000 ns | 1 | - | 0 B |
Código de referencia:
public class BenchmarkNullableCheck
{
static int? x = (new Random()).Next();
public static bool CheckObjectImpl(object o)
{
return o != null;
}
public static bool CheckNullableImpl<T>(T? o) where T: struct
{
return o.HasValue;
}
[Benchmark]
public bool CheckObject()
{
return CheckObjectImpl(x);
}
[Benchmark]
public bool CheckNullable()
{
return CheckNullableImpl(x);
}
}
https://github.com/dotnet/BenchmarkDotNet se utilizó
Pd La gente dice que los consejos "prefieren HasValue debido a un pensamiento coherente" no están relacionados ni son inútiles. ¿Puedes predecir el rendimiento de esto?
public static bool CheckNullableGenericImpl<T>(T? t) where T: struct
{
return t != null;
}
La gente de PPS continúa menos, pero nadie intenta predecir el rendimiento de CheckNullableGenericImpl
. Y el compilador no te ayudará a reemplazar !=null
HasValue
con HasValue
. HasValue
debe utilizar directamente.
Si usas linq y quieres mantener tu código corto, te recomiendo que siempre uses !=null
Y esta es la razón:
Imaginemos que tenemos alguna clase Foo
con una variable doble anulable SomeDouble
public class Foo
{
public double? SomeDouble;
//some other properties
}
Si en algún lugar de nuestro código queremos obtener todos los valores de Foo con un valor SomeDouble no nulo de una colección de Foo (suponiendo que algunos foos en la colección también sean nulos), terminamos con al menos tres formas de escribir nuestra función (si use C # 6):
public IEnumerable<Foo> GetNonNullFoosWithSomeDoubleValues(IEnumerable<Foo> foos)
{
return foos.Where(foo => foo?.SomeDouble != null);
return foos.Where(foo=>foo?.SomeDouble.HasValue); // compile time error
return foos.Where(foo=>foo?.SomeDouble.HasValue == true);
return foos.Where(foo=>foo != null && foo.SomeDouble.HasValue); //if we don''t use C#6
}
Y en este tipo de situaciones recomiendo ir siempre por la más corta.