2009-03-24 15 views
328

Siempre utilicé (a) Nullable<>.HasValue porque me gustaba la semántica. Sin embargo, recientemente estaba trabajando en la base de códigos existente de otra persona, donde utilizaron (b) Nullable<> != null exclusivamente en su lugar. ¿Hay alguna razón para usar una sobre la otra, o es puramente preferencia?Cuál es preferido: Nullable <>. HasValue o Nullable <>! = Null?

(a)

int? a; 
if (a.HasValue) 
    ... 

(b)

int? b; 
if (b != null) 
    ... 
+7

le pregunté a una pregunta similar ... tengo algunas buenas respuestas: http: // stackoverflow. com/questions/633286/nullable-types-best-way-to-check-for-null-or-zero-in-c – nailitdown

+3

* Personalmente *, usaría 'HasValue' ya que creo que las palabras tienden a ser más legibles que símbolos Sin embargo, depende de usted y de lo que se ajuste a su estilo actual. –

+1

'.HasValue' tiene más sentido ya que denota que el tipo es de tipo' T? 'En lugar de un tipo que se puede anular como cadenas. – user3791372

Respuesta

349

El compilador reemplaza comparaciones nulos con una llamada a HasValue, por lo que no hay ninguna diferencia real. Simplemente haz lo que sea más legible/tenga más sentido para ti y tus colegas.

+73

Agregaría a eso "lo que sea más consistente/siga un estilo de codificación existente". –

+11

Wow. Odio este azúcar sintáctico. 'int? x = null' me da la ilusión de que una instancia que admite nulos es un tipo de referencia. Pero la verdad es que Nullable es un tipo de valor. Siento que me gustaría obtener una NullReferenceException: 'int? x = nulo; Use (x.HasValue) '. – KFL

+6

@KFL Si el azúcar sintáctico le molesta, simplemente use 'Nullable ' en lugar de 'int?'. –

35

Prefiero (a != null) para que la sintaxis coincida con los tipos de referencia.

+3

Que es bastante engañoso, por supuesto, ya que 'Nullable <>' es * no * un tipo de referencia. – Luaan

+4

Sí, pero el hecho por lo general importa muy poco en el punto en el que está comprobando nulo. – cbp

+16

Es solo engañoso para los conceptualmente confundidos. Usar una sintaxis consistente para dos tipos diferentes no implica que sean del mismo tipo. C# tiene tipos de referencia anulables (todos los tipos de referencia son actualmente anulables, pero eso cambiará en el futuro) y tipos de valores que aceptan valores. Usar una sintaxis consistente para todos los tipos que aceptan nulos tiene sentido. De ninguna manera implica que los tipos de valor anulables sean tipos de referencia, o que los tipos de referencia anulables sean tipos de valor. –

14

En VB.Net. NO use "IsNot Nothing" cuando puede usar ".HasValue". Acabo de resolver una "Operación podría desestabilizar el tiempo de ejecución" Error de confianza medio al reemplazar "No es nada" con ".HasValue" En un solo lugar. 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.

+20

Esa no sería una suposición correcta. –

+6

Preferiría 'HasValue' debido a la legibilidad. 'IsNot Nothing' es realmente una expresión fea (debido a la doble negación). –

+11

@steffan "IsNot Nothing" no es una doble negación. "Nada" no es negativo, es una cantidad discreta, incluso fuera del ámbito de la programación. "Esta cantidad no es nada". es, gramaticalmente, exactamente lo mismo que decir "Esta cantidad no es cero". y tampoco es un doble negativo. – jmbpiano

18

Hice algunas investigaciones sobre esto mediante el uso de diferentes métodos para asignar valores a una int nullable. Esto es lo que sucedió cuando hice varias cosas. Debería aclarar qué está pasando. Tenga en cuenta: Nullable<something> o la abreviatura something? es una estructura para la cual el compilador parece estar haciendo un montón de trabajo para permitirnos usar con nulo como si fuera una clase.
Como verá a continuación, SomeNullable == null y SomeNullable.HasValue siempre devolverán un verdadero o falso esperado. Aunque no se muestra a continuación, SomeNullable == 3 también es válido (suponiendo que SomeNullable es un int?).
Mientras SomeNullable.Value nos muestra un error de tiempo de ejecución si asignamos null a SomeNullable. Este es, de hecho, el único caso en el que los nulables podrían causarnos un problema, gracias a una combinación de operadores sobrecargados, el método object.Equals(obj) sobrecargado y la optimización del compilador y el negocio de los monos.

Aquí es una descripción de un código corría, y lo que la producción se produce 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 tratar 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.") 

Todo lo mismo que antes . Tenga en cuenta que la inicialización con int? val = new int?(null);, con nulo pasado al constructor, habría producido un error de tiempo COMPIL, ya que el VALOR del objeto que admite nulos no puede contener nulos. Solo el objeto contenedor puede ser nulo.

Del mismo modo, nos encontraremos con un error de tiempo de compilación de:

int? val = new int?(); 
val.Value = null; 
no

mencionar que val.Value es una propiedad de sólo lectura de todos modos, lo que significa que ni siquiera podemos usar algo como:

val.Value = 3; 

pero de nuevo, los operadores de conversión implícitas sobrecargados polimorfas nos permiten hacerlo:

val = 3; 

No hay necesidad de preocuparse por polisomthing whatchamacallits sin embargo, siempre y cuando funcione ¿verdad? :)

+4

"Tenga en cuenta: Nullable o la abreviatura algo? Es una clase." ¡Esto está mal! Nullable es una estructura. Sobrecarga igual y == operador para devolver verdadero en comparación con nulo. El compilador no hace un trabajo elegante para esta comparación. – andrewjs

+1

@andrewjs - Tiene razón en que es una estructura (no una clase), pero está equivocado porque sobrecarga el operador ==. Si escribe 'Nullable ' en VisualStudio 2013 y F12, verá que solo sobrecarga la conversión hacia y desde 'X', y el método' Igual (otro) '. Sin embargo, creo que el operador == usa ese método por defecto, por lo que el efecto es el mismo. En realidad, he querido actualizar esta respuesta sobre ese hecho desde hace un tiempo, pero soy flojo y/o ocupado. Este comentario tendrá que hacer por ahora :) –

+0

Hice una comprobación rápida a través de ildasm y tienes razón sobre el compilador haciendo algo de magia; comparar un objeto Nullable con nulo, de hecho se traduce en una llamada a HasValue. ¡Interesante! – andrewjs

-3

Respuesta general y norma general: si tiene una opción (por ejemplo, escribir serializadores personalizados) para procesar Nullable en una canalización distinta de object y usar sus propiedades específicas, hágalo y utilice propiedades específicas anulables. Por lo tanto, desde el punto de vista del pensamiento consistente HasValue debe ser preferido. El pensamiento constante puede ayudarte a escribir un mejor código, no pases demasiado tiempo en los detalles. P. ej. hay segundo método será muchas veces más eficaz (sobre todo debido a los compiladores de procesos en línea y el boxeo, pero todavía 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 usó

PS La gente dice que esto no está relacionado y es inútil. Se puede predecir el rendimiento de esto:

public static bool CheckNullableGenericImpl<T>(T? t) where T: struct 
{ 
    return t != null; 
} 
+0

Your 'CheckObjectImpl' [boxes] (http://stackoverflow.com/q/2111857/11683) el nulable en un' objeto', mientras que 'CheckNullableImpl' no usa boxeo. Por lo tanto, la comparación es muy infundada. No solo no es una tarifa, sino que también es inútil porque, como se señala en la [respuesta aceptada] (http://stackoverflow.com/a/676089/11683), el compilador reescribe '! =' A 'HasValue' de todos modos. – GSerg

+0

GSerg, su comentario es infrecuente. Solo puedo repetir que es una situación específica, pero es un caso común en algunas áreas, p. en serializadores. Además de responder, demuestro cómo las cosas pueden volverse más complicadas si ignoras la naturaleza de estructura de Nullable . Los lectores decidirán si es inútil o no, para mí es interesante y lo comparto. –

+0

Los lectores no ignoran la naturaleza struct de 'Nullable ', * usted * (al encajonarlo en un 'objeto'). Cuando aplica '! = Null' con un anulable a la izquierda, no se produce ningún boxeo porque el soporte para'! = 'Para nullables funciona en el nivel del compilador. Es diferente cuando oculta el anulable del compilador al encasillarlo en un 'objeto'. Ni 'CheckObjectImpl (object o) 'ni su punto de referencia tienen sentido en principio. – GSerg

0

Si utiliza LINQ y desea mantener su código corto, yo recomiendo usar siempre !=null

y es por esto:

Vamos imaginemos que tenemos alguna clase Foo con un anulable doble variables SomeDouble

public class Foo 
{ 
    public double? SomeDouble; 
    //some other properties 
} 

Si en algún lugar de nuestro código queremos obtener todos los Foo con valores no nulos de SomeDouble de una colección de Foo (asumiendo que algunos foos de la colección también pueden ser nulos), terminamos con al menos tres formas de escribir nuestra función (si usamos C# 6):

public IEnumerable<Foo> GetNonNullFoosWithSomeDoubleValues(IEnumerable<Foo> foos) 
{ 
    return foos.Where(foo => foo?.SomeDouble != null); 
    return foos.Where(foo=>foo?.SomeDouble.HasValue); // will throw an 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 situación recomiendo ir siempre para el más corto

Cuestiones relacionadas