Co jest preferowane: Nullable<>.HasValue lub Nullable<>!= null?

Zawsze używałem (a) Nullable<>.HasValue ponieważ lubiłem semantykę. Jednak ostatnio pracowałem nad istniejącą bazą kodu innej osoby, gdzie używali wyłącznie (b) Nullable<> != null. Czy jest powód, aby używać jednego nad drugim, czy jest to czysto preferencyjne?

(a)

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

(b)

int? b;
if (b != null)
    ...
Author: Palec, 2009-03-24

6 answers

Kompilator zastępuje porównania null wywołaniem HasValue, więc nie ma rzeczywistej różnicy. Po prostu zrób to, co jest bardziej czytelne/ma większy sens dla Ciebie i twoich kolegów.

 382
Author: Rex M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-24 03:34:09

Wolę (a != null), aby składnia pasowała do typów referencji.

 41
Author: cbp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-30 09:16:28

Zrobiłem kilka badań na ten temat, używając różnych metod, aby przypisać wartości do nullable int. Oto, co się stało, gdy robiłem różne rzeczy. Powinno wyjaśnić, o co chodzi. Należy pamiętać: {[5] } lub skrót {[6] } jest strukturą, dla której kompilator wydaje się wykonywać wiele pracy, aby pozwolić nam używać z null tak, jakby była klasą.
Jak zobaczysz poniżej, SomeNullable == null i SomeNullable.HasValue zawsze zwrócą oczekiwaną wartość true lub false. Chociaż nie wykazano poniżej, SomeNullable == 3 jest również poprawne (zakładając SomeNullable to int?).
Podczas gdy SomeNullable.Value wyświetla nam błąd runtime, jeśli przypisaliśmy null do SomeNullable. W rzeczywistości jest to jedyny przypadek, w którym nullables może sprawić nam problem, dzięki kombinacji przeciążonych operatorów, przeciążonej metody object.Equals(obj) oraz optymalizacji kompilatora i monkey business.

Oto opis kodu, który uruchomiłem, i jakie wyjście wyprodukował w etykietach:

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, spróbujmy następnej metody inicjalizacji:

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

Wszystkie takie same jak wcześniej. Należy pamiętać, że inicjalizacja za pomocą int? val = new int?(null);, z null przekazanym konstruktorowi, spowodowałaby błąd w czasie kompilacji, ponieważ wartość obiektu nullable nie jest nullable. Tylko sam obiekt wrapper może być równy null.

Podobnie, otrzymamy błąd czasu kompilacji z:

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

Nie wspominając o tym, że val.Value jest właściwością tylko do odczytu, co oznacza, że nie możemy nawet użyć czegoś takiego jak:

val.Value = 3;
/ Align = "left" / operatory let us do:
val = 3;

Nie musisz się martwić o polisomthing cochamacallits choć, tak długo, jak to działa dobrze? :)

 19
Author: Perrin Larson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-25 21:09:37

In VB.Net. nie używaj "IsNot Nothing", kiedy możesz użyć".HasValue". Właśnie rozwiązałem "operacja może zdestabilizować błąd średniego zaufania runtime", zastępując "IsNot Nothing "przez".HasValue " w jednym miejscu. Nie bardzo rozumiem dlaczego, ale coś dzieje się inaczej w kompilatorze. Przypuszczam, że"!= null " W C# może mieć ten sam problem.

 14
Author: Carter Medlin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-23 18:13:55

Jeśli używasz linq i chcesz zachować krótki kod, polecam zawsze używać !=null

I dlatego:

Wyobraźmy sobie, że mamy jakąś klasę Fooz nullable double zmienną SomeDouble

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

Jeśli gdzieś w naszym kodzie chcemy uzyskać wszystkie Foo z inną niż null wartościami z kolekcji Foo (zakładając, że niektóre Foo w kolekcji też mogą być null), otrzymujemy co najmniej trzy sposoby zapisu naszej funkcji (jeśli używamy 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
}

I w tego typu sytuacji polecam zawsze iść na krótszy

 1
Author: yan yankelevich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-09 08:18:00

Ogólna odpowiedź i zasada: jeśli masz opcję (np. pisanie niestandardowych serializerów), aby przetwarzać Nullable w innym potoku niż object - i używać ich specyficznych właściwości - zrób to i użyj specyficznych właściwości Nullable. Więc z konsekwentnego myślenia punktu widzenia HasValue powinny być preferowane. Konsekwentne myślenie może pomóc ci napisać lepszy kod nie spędzając zbyt wiele czasu w szczegółach. Np. druga metoda będzie wielokrotnie skuteczniejsza (głównie ze względu na wbudowane Kompilatory i boks, ale i tak liczby są bardzo wyraziste):

public static bool CheckObjectImpl(object o)
{
    return o != null;
}

public static bool CheckNullableImpl<T>(T? o) where T: struct
{
    return o.HasValue;
}

Test porównawczy:

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 |

Benchmark code:

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 został użyty

PS . Ludzie mówią, że porady "preferują HasValue z powodu konsekwentnego myślenia" nie są powiązane i bezużyteczne. możesz przewidzieć wydajność tego?

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

PPS ludzie nadal minus, ale nikt nie próbuje przewidzieć wydajności CheckNullableGenericImpl. I tam kompilator nie pomoże Ci zastąpić !=null przez HasValue. HasValue należy stosować bezpośrednio.

 -5
Author: Roman Pokrovskij,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-21 11:29:14