Quale è preferito: Nullable <T> .HasValue o Nullable <T>! = Null?


437

Ho sempre usato Nullable<>.HasValueperché mi piaceva la semantica. Tuttavia, recentemente stavo lavorando sulla base di codice esistente di qualcun altro, che Nullable<> != nullinvece utilizzava esclusivamente.

C'è un motivo per usare l'uno sull'altro o è puramente una preferenza?

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

vs.

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

9
Ho fatto una domanda simile ... avuto alcune buone risposte: stackoverflow.com/questions/633286/...
nailitdown

3
Personalmente , userei HasValuedal momento che penso che le parole tendano ad essere più leggibili dei simboli. Tuttavia, dipende tutto da te e da ciò che si adatta al tuo stile esistente.
Jake Petroules,

1
.HasValueha più senso in quanto indica che il tipo è di tipo T?piuttosto che un tipo che può essere nullable come le stringhe.
user3791372

Risposte:


476

Il compilatore sostituisce i confronti null con una chiamata a HasValue, quindi non vi è alcuna differenza reale. Fai quello che è più leggibile / ha più senso per te e i tuoi colleghi.


86
Aggiungo a ciò "qualunque sia più coerente / segue uno stile di codifica esistente".
Josh Lee,

20
Wow. Odio questo zucchero sintattico. int? x = nullmi dà l'illusione che un'istanza nullable sia un tipo di riferimento. Ma la verità è che Nullable <T> è un tipo di valore. Ci si sente che avrei avuto un NullReferenceException fare: int? x = null; Use(x.HasValue).
KFL

11
@KFL Se lo zucchero sintattico ti disturba, basta usare Nullable<int>invece di int?.
Cole Johnson,

24
Nelle prime fasi della creazione di un'applicazione potresti pensare che sia sufficiente utilizzare un tipo di valore nullable per archiviare alcuni dati, solo per rendersi conto dopo un po 'che hai bisogno di una classe adeguata per il tuo scopo. Avere scritto il codice originale da confrontare con null ha quindi il vantaggio di non dover cercare / sostituire ogni chiamata a HasValue () con un confronto null.
Anders,

15
È piuttosto sciocco lamentarsi di poter impostare una nullullabile su null o confrontarla con null dato che si chiama nullable . Il problema è che le persone stanno fondendo "tipo di riferimento" con "può essere nullo", ma questa è una confusione concettuale. Il C # futuro avrà tipi di riferimento non annullabili.
Jim Balter,

48

Preferisco (a != null)che la sintassi corrisponda ai tipi di riferimento.


11
Il che è abbastanza fuorviante, ovviamente, poiché nonNullable<> è un tipo di riferimento.
Luaan,

9
Sì, ma il fatto di solito conta molto poco nel punto in cui stai verificando null.
cpp

31
È solo fuorviante per il concettualmente confuso. L'uso di una sintassi coerente per due tipi diversi non implica che siano dello stesso tipo. C # ha tipi di riferimento nullable (tutti i tipi di riferimento sono attualmente nullable, ma ciò cambierà in futuro) e tipi di valore nullable. L'uso di una sintassi coerente per tutti i tipi annullabili ha senso. In nessun modo implica che i tipi di valore nullable siano tipi di riferimento o che i tipi di riferimento nullable siano tipi di valore.
Jim Balter,

Preferisco HasValueperché è più leggibile di!= null
Konrad,

La coerenza del codice è più leggibile se non si mescolano stili diversi di scrittura dello stesso codice. Dal momento che non tutti i luoghi hanno una proprietà .HasValue, quindi da allora usa! = Null per una maggiore coerenza. Secondo me.
ColacX,

21

Ho fatto qualche ricerca su questo usando diversi metodi per assegnare valori a un int nullabile. Ecco cosa è successo quando ho fatto varie cose. Dovrebbe chiarire cosa sta succedendo. Ricorda: Nullable<something>o la stenografia something?è una struttura per la quale il compilatore sembra fare molto lavoro per farci usare con null come se fosse una classe.
Come si vedrà di seguito, SomeNullable == nulle SomeNullable.HasValuesarà sempre restituire un atteso vero o falso. Anche se non dimostrato di seguito, SomeNullable == 3è valido anche (supponendo che SomeNullable sia un int?).
Mentre SomeNullable.Valueci viene assegnato un errore di runtime se assegnato nulla SomeNullable. Questo è in effetti l'unico caso in cui nullable potrebbe causarci un problema, grazie a una combinazione di operatori sovraccarichi, sovraccarichiobject.Equals(obj) metodo, ottimizzazione del compilatore e business delle scimmie.

Ecco una descrizione di alcuni codici che ho eseguito e quale output ha prodotto nelle etichette:

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, proviamo il prossimo metodo di inizializzazione:

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

Lo stesso di prima. Tieni presente che l'inizializzazione con int? val = new int?(null);, con null passato al costruttore, avrebbe prodotto un errore di tempo COMPILE, poiché il valore VALUE dell'oggetto nullable NON è nullable. È solo l'oggetto wrapper stesso che può essere uguale a null.

Allo stesso modo, avremmo un errore di compilazione da:

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

per non parlare del fatto che val.Valueè una proprietà di sola lettura, il che significa che non possiamo nemmeno usare qualcosa come:

val.Value = 3;

ma ancora, gli operatori di conversione implicita sovraccarica polimorfa ci permettono di fare:

val = 3;

Non c'è bisogno di preoccuparsi di polisomica, qualunque cosa accada, purché funzioni bene? :)


5
"Ricorda: Nullable <qualcosa> o la stenografia qualcosa? È una classe." Questo è sbagliato! Nullable <T> è una struttura. Sovraccarica Equals e == operatore per restituire true rispetto a null. Il compilatore non ha un lavoro elaborato per questo confronto.
andrewjs,

1
@andrewjs - Hai ragione sul fatto che si tratta di una struttura (non di una classe), ma ti sbagli che sovraccarica l'operatore ==. Se si digita Nullable<X>VisualStudio 2013 e F12, vedrai che sovraccarica solo la conversione da e verso Xe il Equals(object other)metodo. Tuttavia, penso che l'operatore == utilizzi questo metodo per impostazione predefinita, quindi l'effetto è lo stesso. In realtà ho intenzione di aggiornare questa risposta su questo fatto da un po 'di tempo, ma sono pigro e / o occupato. Questo commento dovrà fare per ora :)
Perrin Larson,

Ho fatto un rapido controllo attraverso ildasm e hai ragione sul fatto che il compilatore stia facendo un po 'di magia; il confronto di un oggetto Nullable <T> con null si traduce in realtà in una chiamata a HasValue. Interessante!
andrewjs,

3
@andrewjs In realtà, il compilatore fa un sacco di lavoro per ottimizzare nullable. Ad esempio, se si assegna un valore a un tipo nullable, in realtà non sarà affatto nullable (ad esempio, int? val = 42; val.GetType() == typeof(int)). Quindi non solo è nullable una struttura che può essere uguale a null, ma spesso non è affatto nullable! : D Allo stesso modo, quando inscatoli un valore nullable, stai inscatolando int, no int?- e quando int?non ha un valore, ottieni nullinvece di un valore nullable inscatolato. Fondamentalmente significa che raramente ci sono sovraccarichi nell'usare nullable correttamente :)
Luaan

1
@JimBalter Davvero? È molto interessante. Quindi, cosa ti dice il profiler della memoria di un campo nullable in una classe? Come si dichiara un tipo di valore che eredita da un altro tipo di valore in C #? Come si dichiara il proprio tipo nullable che si comporta come il tipo nullable di .NET? Da quando è Nullun tipo in .NET? Puoi indicare la parte nella specifica CLR / C # in cui è stato detto? Le nullità sono ben definite nelle specifiche CLR, il loro comportamento non è "implementazione di un'astrazione" - è un contratto . Ma se il meglio che puoi fare è attacchi ad hominem, divertiti.
Luaan,

13

In VB.Net. NON utilizzare "IsNot Nothing" quando è possibile utilizzare ".HasValue". Ho appena risolto un "Operazione potrebbe destabilizzare il runtime" Errore di attendibilità medio sostituendo "IsNot Nothing" con ".HasValue" In un punto. Non capisco davvero perché, ma qualcosa sta accadendo diversamente nel compilatore. Suppongo che "! = Null" in C # potrebbe avere lo stesso problema.


8
Preferirei HasValueper leggibilità. IsNot Nothingè davvero una brutta espressione (a causa della doppia negazione).
Stefan Steinegger,

12
@steffan "IsNot Nothing" non è doppia negazione. "Niente" non è negativo, è una quantità discreta, anche al di fuori del regno della programmazione. "Questa quantità non è niente." è, grammaticalmente, lo stesso che dire "Questa quantità non è zero". e nessuno dei due è un doppio negativo.
jmbpiano,

5
Non è che non voglio essere in disaccordo con l'assenza di verità qui, ma andiamo ora. IsNot Nothing è chiaramente, beh, eccessivamente negativo. Perché non scrivere qualcosa di positivo e chiaro come HasValue? Questo non è un test di grammatica, è una codifica, in cui l'obiettivo chiave è la chiarezza.
Randy Gamage,

3
jmbpiano: Sono d'accordo che non è una doppia negazione, ma è un'unica negazione ed è quasi brutta e non chiara come una semplice espressione positiva.
Kaveh Hadjari,

0

Se usi linq e vuoi mantenere il codice breve, ti consiglio di usare sempre !=null

Ed è per questo che:

Immaginiamo di avere una classe Foocon una doppia variabile nullableSomeDouble

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

Se da qualche parte nel nostro codice vogliamo ottenere tutti i Foo con valori SomeDouble non nulli da una raccolta di Foo (supponendo che anche alcuni foo nella raccolta possano essere nulli), finiamo con almeno tre modi per scrivere la nostra funzione (se noi usa 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
}

E in questo tipo di situazione consiglio di scegliere sempre quello più corto


2
Sì, foo?.SomeDouble.HasValueè un errore di compilazione (non un "lancio" nella mia terminologia) in quel contesto perché il suo tipo è bool?, non solo bool. (Il .Wheremetodo vuole un Func<Foo, bool>.) È permesso farlo (foo?.SomeDouble).HasValue, ovviamente, dato che ha tipo bool. Questo è ciò che la tua prima riga viene "tradotta" internamente dal compilatore C # (almeno formalmente).
Jeppe Stig Nielsen,

-6

Risposta generale e regola empirica: se si dispone di un'opzione (ad esempio la scrittura di serializzatori personalizzati) per elaborare Nullable in pipeline diversa rispetto a object- e utilizzare le loro proprietà specifiche - farlo e utilizzare proprietà specifiche Nullable. Quindi dal punto di vista del pensiero coerente HasValuedovrebbe essere preferito. Un pensiero coerente può aiutarti a scrivere codice migliore senza spendere troppo tempo nei dettagli. Ad esempio, il secondo metodo sarà molte volte più efficace (principalmente a causa dei compilatori inline e boxe, ma i numeri fissi sono molto espressivi):

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

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

Test benchmark:

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 |

Codice benchmark:

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 è stato utilizzato

PS . La gente dice che i consigli "preferiscono HasValue a causa del pensiero coerente" non sono correlati e inutili. Puoi prevedere le prestazioni di questo?

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

PPS Le persone continuano a essere negative, ma nessuno prova a prevedere le prestazioni di CheckNullableGenericImpl. E lì il compilatore non ti aiuterà a sostituirlo !=nullcon HasValue. HasValuedovrebbe essere usato direttamente se sei interessato alla performance.


2
Le tue CheckObjectImpl caselle il nullable in un object, mentre CheckNullableImplnon usa la boxe. Quindi il confronto è molto raro. Non solo non è tariffa, è anche inutile perché, come indicato nella risposta accettata , le riscritture del compilatore !=per HasValueogni caso.
GSerg,

2
I lettori non ignorano la natura strutt Nullable<T>, o lo fanno (inscatolandolo in unobject ). Quando si applica != nullcon un nullable a sinistra, non si verifica alcun boxing poiché il supporto !=per nullable funziona a livello di compilatore. È diverso quando si nasconde il nullable dal compilatore inserendolo prima in un object. Né il CheckObjectImpl(object o)tuo benchmark ha senso in linea di principio.
GSerg,

3
Il mio problema è che mi interessa la qualità dei contenuti su questo sito Web. Quello che hai pubblicato è fuorviante o sbagliato. Se stavi tentando di rispondere alla domanda del PO, la tua risposta è completamente sbagliata, il che è facile da dimostrare sostituendo il chiamata a CheckObjectImplcon il suo corpo all'interno CheckObject. Tuttavia, i tuoi ultimi commenti rivelano che in realtà avevi in ​​mente una domanda completamente diversa quando hai deciso di rispondere a questa domanda di 8 anni, il che rende la tua risposta fuorviante nel contesto della domanda originale. Non era quello che chiedeva l'OP.
GSerg

3
Mettiti nei panni del prossimo che cerca su Google what is faster != or HasValue. Arriva a questa domanda, sfoglia la tua risposta, apprezza il tuo benchmark e dice "Accidenti, non lo userò mai !=perché è chiaramente molto più lento!" Questa è una conclusione molto sbagliata che poi procederà a diffondersi. Ecco perché credo che la tua risposta sia dannosa: risponde a una domanda sbagliata e quindi pianta una conclusione errata nel lettore ignaro. Considera cosa succede quando cambi il tuoCheckNullableImpl per anche essere return o != null;Otterrete lo stesso risultato benchmark.
GSerg,

8
Sto discutendo con la tua risposta. La tua risposta appare ingannevolmente come se mostra la differenza tra!= e HasValuequando in realtà mostra la differenza tra object oe T? o. Se fai quello che ho suggerito, cioè riscrivi CheckNullableImplcome public static bool CheckNullableImpl<T>(T? o) where T: struct { return o != null; }, finirai con un benchmark che mostra chiaramente che !=è molto più lento di !=. Che dovrebbe condurvi alla conclusione che il problema la risposta descrive non si tratta di !=vs HasValueaffatto.
GSerg
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.