Qual é o preferido: Nullable . HasValue ou Nullable ! = Null?

Eu sempre usei (a) Nullable.HasValue porque eu gostei da semântica. No entanto, recentemente eu estava trabalhando na base de código existente de outra pessoa, onde eles usaram (b) Nullable != null exclusivamente em vez disso. Existe uma razão para usar um sobre o outro, ou é puramente preferência?

(uma)

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

b)

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

   

O compilador substitui comparações nulas com uma chamada para HasValue , portanto, não há nenhuma diferença real. Basta fazer o que for mais legível / faz mais sentido para você e seus colegas.

Eu prefiro (a != null) para que a syntax corresponda aos tipos de referência.

Eu fiz algumas pesquisas sobre isso usando methods diferentes para atribuir valores a um int anulável. Aqui está o que aconteceu quando eu fiz várias coisas. Deve esclarecer o que está acontecendo. Tenha em mente: Nullable ou something? taquigráfico something? é uma estrutura para a qual o compilador parece estar fazendo muito trabalho para nos deixar usar com null como se fosse uma class.
Como você verá abaixo, SomeNullable == null e SomeNullable.HasValue sempre retornarão um valor esperado verdadeiro ou falso. Embora não seja demonstrado abaixo, SomeNullable == 3 é válido também (supondo que SomeNullable é um int? ).
Enquanto SomeNullable.Value nos obtém um erro de tempo de execução se atribuirmos null a SomeNullable . Este é, de fato, o único caso em que os nullables podem nos causar um problema, graças a uma combinação de operadores sobrecarregados, object.Equals(obj) sobrecarregados de object.Equals(obj) e otimização de compiladores e negócios macacos.

Aqui está uma descrição de algum código que eu executei e qual saída ele produziu nos labels:

 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 tentar o próximo método de boot:

 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.") 

Tudo o mesmo que antes. Tenha em mente que inicializando com int? val = new int?(null); int? val = new int?(null); , com nulo passado para o construtor, teria produzido um erro de tempo COMPILE, uma vez que o VALUE do object anulável NÃO é anulável. É apenas o próprio object wrapper que pode ser igual a null.

Da mesma forma, obteríamos um erro de tempo de compilation de:

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

sem mencionar que val.Value é uma propriedade somente de leitura de qualquer maneira, o que significa que não podemos usar algo como:

 val.Value = 3; 

mas novamente, os operadores de conversão implícita sobrecarregados polimorficamente nos permitem:

 val = 3; 

Não precisa se preocupar com o polissomo, embora isso funcione certo? 🙂

No VB.Net. Não use “IsNot Nothing” quando você pode usar “.HasValue”. Eu apenas resolvi um erro de confiança “Operação poderia desestabilizar o tempo de execução” substituindo “IsNot Nothing” por “.HasValue” em um ponto. Eu realmente não entendo porque, mas algo está acontecendo de forma diferente no compilador. Eu diria que “! = Null” em c # pode ter o mesmo problema.

Se você usa linq e quer manter seu código curto, eu recomendo sempre usar !=null

E é por isso:

Vamos imaginar que temos alguma class Foo com uma variável dupla anulável SomeDouble

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

Se em algum lugar do nosso código queremos obter todos os Foo com valores não nulos SomeDouble de uma coleção de Foo (supondo que alguns foos na coleção podem ser nulos também), acabamos com pelo menos três maneira de escrever nossa function (se use C # 6):

 public IEnumerable GetNonNullFoosWithSomeDoubleValues(IEnumerable 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 } 

E neste tipo de situação eu recomendo sempre ir para o mais curto

Resposta geral e regra geral: se você tiver uma opção (por exemplo, escrever serializadores personalizados) para processar Nullable em pipeline diferente do object – e usar suas propriedades específicas – faça isso e use propriedades específicas Nullable. Então, do ponto de vista do pensamento consistente, HasValue deve ser preferido. O pensamento consistente pode ajudá-lo a escrever um código melhor, não gastando muito tempo em detalhes. Por exemplo, o segundo método será muitas vezes mais eficaz (principalmente por causa dos compiladores inlining e boxe, mas os números ainda são muito expressivos):

 public static bool CheckObjectImpl(object o) { return o != null; } public static bool CheckNullableImpl(T? o) where T: struct { return o.HasValue; } 

Teste de referência:

 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 referência:

 public class BenchmarkNullableCheck { static int? x = (new Random()).Next(); public static bool CheckObjectImpl(object o) { return o != null; } public static bool CheckNullableImpl(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 foi usado

PS . As pessoas dizem que o conselho “prefere o HasValue por causa do pensamento consistente” não é relacionado e inútil. Você pode prever o desempenho disso?

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

PPS As pessoas continuam menos, mas ninguém tenta prever o desempenho de CheckNullableGenericImpl . E lá o compilador não irá ajudá-lo a replace !=null HasValue por HasValue . HasValue deve ser usado diretamente.