Perché c'è una differenza nel controllo di null rispetto a un valore in VB.NET e C #?


110

In VB.NET questo accade:

Dim x As System.Nullable(Of Decimal) = Nothing
Dim y As System.Nullable(Of Decimal) = Nothing

y = 5
If x <> y Then
    Console.WriteLine("true")
Else
    Console.WriteLine("false") '' <-- I got this. Why?
End If

Ma in C # questo accade:

decimal? x = default(decimal?);
decimal? y = default(decimal?);

y = 5;
if (x != y)
{
    Debug.WriteLine("true"); // <-- I got this -- I'm with you, C# :)
}
else
{
    Debug.WriteLine("false");
}

Perché c'è una differenza?


22
è terrificante.
Mikeb

8
Credo default(decimal?)stia restituendo 0, non null.
Ryan Frame

7
@ RyanFrame NO. Poiché è di tipo nullable , restituiscenull
Soner Gönül

4
Oh sì ... giusto ... nei Ifcondizionali VB non è necessario valutare come booleano ... uuuugh EDIT: Quindi il Nothing <> Anything = Nothingche si traduce nel Ifprendere il percorso negativo / altro.
Chris Sinclair

13
@ JMK: Null, Nothing e Empty sono in realtà sottilmente tutti diversi. Se fossero tutti uguali, non ne avresti bisogno tre.
Eric Lippert

Risposte:


88

VB.NET e C # .NET sono linguaggi diversi, costruiti da diversi team che hanno fatto ipotesi diverse sull'utilizzo; in questo caso la semantica di un confronto NULL.

La mia preferenza personale è per la semantica VB.NET, che in sostanza dà a NULL la semantica "Non lo so ancora". Poi il confronto di 5 con "Non lo so ancora". è naturalmente "non lo so ancora"; cioè NULL. Questo ha l'ulteriore vantaggio di rispecchiare il comportamento di NULL in (la maggior parte se non tutti) i database SQL. Questa è anche un'interpretazione più standard (rispetto a C #) della logica a tre valori, come spiegato qui .

Il team di C # ha fatto ipotesi diverse sul significato di NULL, risultando nella differenza di comportamento mostrata. Eric Lippert ha scritto un blog sul significato di NULL in C # . Per Eric Lippert: "Ho anche scritto della semantica dei null in VB / VBScript e JScript qui e qui ".

In qualsiasi ambiente in cui sono possibili valori NULL, è importante riconoscere che la Legge del Medio Escluso (cioè che A o ~ A è tautologicamente vero) non può più essere invocato.

Aggiornare:

A bool(al contrario di a bool?) può assumere solo i valori VERO e FALSO. Tuttavia, un'implementazione del linguaggio di NULL deve decidere come NULL si propaga attraverso le espressioni. In VB le espressioni 5=nulle 5<>nullENTRAMBI restituiscono false. In C #, delle espressioni comparabili 5==nulle 5!=nullsolo la seconda prima [aggiornato 2014-03-02 - PG] restituisce false. Tuttavia, in QUALSIASI ambiente che supporta null, spetta al programmatore conoscere le tabelle della verità e la propagazione null utilizzate da quel linguaggio.

Aggiornare

Gli articoli del blog di Eric Lippert (menzionati nei suoi commenti di seguito) sulla semantica sono ora disponibili su:


4
Grazie per il collegamento. Ho anche scritto sulla semantica dei null in VB / VBScript e JScript qui: blogs.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx e qui: blogs.msdn.com/b/ericlippert/ archivio / 10/01/2003 / 53128.aspx
Eric Lippert

27
E per tua informazione, la decisione di rendere C # incompatibile con VB in questo modo è stata controversa. Non ero nel team di progettazione del linguaggio in quel momento, ma la quantità di dibattito che ha portato a questa decisione è stata considerevole.
Eric Lippert

2
@ BlueRaja-DannyPflughoeft In C # boolnon può avere 3 valori, solo due. È bool?che può avere tre valori. operator ==ed operator !=entrambi restituiscono bool, no bool?, indipendentemente dal tipo di operandi. Inoltre, una ifdichiarazione può accettare solo un bool, non un bool?.
Servy

1
In C # le espressioni 5=nulle 5<>nullnon sono valide. E di 5 == nulle 5 != null, sei sicuro che sia il secondo che ritorna false?
Ben Voigt

1
@ BenVoigt: grazie. Tutti quei voti positivi e sei il primo a individuare quell'errore di battitura. ;-)
Pieter Geerkens

37

Perché x <> yritorna Nothinginvece di true. Semplicemente non è definito poiché xnon è definito. (simile a SQL null).

Nota: VB.NET Nothing<> C # null.

Devi anche confrontare il valore di a Nullable(Of Decimal)solo se ha un valore.

Quindi il VB.NET sopra si confronta in modo simile a questo (che sembra meno errato):

If x.HasValue AndAlso y.HasValue AndAlso x <> y Then
    Console.WriteLine("true")
Else
    Console.WriteLine("false")  
End If

La specifica del linguaggio VB.NET :

7.1.1 Tipi di valore nullable ... Un tipo di valore nullable può contenere gli stessi valori della versione non nullable del tipo nonché il valore null. Pertanto, per un tipo di valore nullable, l'assegnazione di Nothing a una variabile del tipo imposta il valore della variabile sul valore null, non sul valore zero del tipo di valore.

Per esempio:

Dim x As Integer = Nothing
Dim y As Integer? = Nothing

Console.WriteLine(x) ' Prints zero '
Console.WriteLine(y) ' Prints nothing (because the value of y is the null value) '

16
"VB.NET Nothing <> C # null" restituisce true per C # e false per VB.Net?
Sto

17

Guarda il CIL generato (li ho convertiti entrambi in C #):

C #:

private static void Main(string[] args)
{
    decimal? x = null;
    decimal? y = null;
    y = 5M;
    decimal? CS$0$0000 = x;
    decimal? CS$0$0001 = y;
    if ((CS$0$0000.GetValueOrDefault() != CS$0$0001.GetValueOrDefault()) ||
        (CS$0$0000.HasValue != CS$0$0001.HasValue))
    {
        Console.WriteLine("true");
    }
    else
    {
        Console.WriteLine("false");
    }
}

Visual Basic:

[STAThread]
public static void Main()
{
    decimal? x = null;
    decimal? y = null;
    y = 5M;
    bool? VB$LW$t_struct$S3 = new bool?(decimal.Compare(x.GetValueOrDefault(), y.GetValueOrDefault()) != 0);
    bool? VB$LW$t_struct$S1 = (x.HasValue & y.HasValue) ? VB$LW$t_struct$S3 : null;
    if (VB$LW$t_struct$S1.GetValueOrDefault())
    {
        Console.WriteLine("true");
    }
    else
    {
        Console.WriteLine("false");
    }
}

Vedrai che il confronto in Visual Basic restituisce Nullable <bool> (non bool, false o true!). E undefined convertito in bool è falso.

Nothingrispetto a tutto ciò che è sempre Nothing, non falso in Visual Basic (è lo stesso di SQL).


Perché rispondere alla domanda per tentativi ed errori? Dovrebbe essere possibile farlo dalle specifiche della lingua.
David Heffernan

3
@DavidHeffernan, perché questo mostra la differenza di lingua che è piuttosto inequivocabile.
nothrow

2
@Yossarian Pensi che le specifiche della lingua siano ambigue sulla questione. Non sono d'accordo. L'IL è un dettaglio di implementazione soggetto a modifiche; le specifiche non lo sono.
Servizio

2
@DavidHeffernan: mi piace il tuo atteggiamento e ti incoraggio a provare. La specifica del linguaggio VB può essere difficile da analizzare a volte. Lucian lo sta migliorando da alcuni anni, ma può ancora essere abbastanza difficile scoprire i significati esatti di questi tipi di casi d'angolo. Ti suggerisco di ottenere una copia delle specifiche, fare qualche ricerca e segnalare i tuoi risultati.
Eric Lippert

2
@Yossarian I risultati dell'esecuzione del codice IL che hai fornito non sono soggetti a modifiche, ma il codice C # / VB fornito verrà compilato nel codice IL che hai mostrato è soggetto a modifiche (purché il comportamento di quell'IL sia anche in linea con la definizione delle specifiche della lingua).
Servy

6

Il problema che si osserva qui è un caso speciale di un problema più generale, che è che il numero di diverse definizioni di uguaglianza che possono essere utili in almeno alcune circostanze supera il numero di mezzi comunemente disponibili per esprimerle. Questo problema è in alcuni casi aggravato dalla sfortunata convinzione che sia fonte di confusione avere diversi mezzi per testare l'uguaglianza producendo risultati diversi, e tale confusione potrebbe essere evitata facendo in modo che le diverse forme di uguaglianza producano gli stessi risultati quando possibile.

In realtà, la causa fondamentale della confusione è la convinzione errata che le diverse forme di test di uguaglianza e disuguaglianza dovrebbero produrre lo stesso risultato, nonostante il fatto che semantica diversa sia utile in circostanze diverse. Ad esempio, da un punto di vista aritmetico, è utile poter avere Decimalche differiscono solo per il numero di zeri finali confrontare come uguali. Allo stesso modo per doublevalori come zero positivo e zero negativo. D'altra parte, dal punto di vista della memorizzazione nella cache o dell'internamento, tale semantica può essere mortale. Supponiamo, ad esempio, di avere un Dictionary<Decimal, String>tale che myDict[someDecimal]dovrebbe essere uguale someDecimal.ToString(). Un tale oggetto sembrerebbe ragionevole se ne avessi moltiDecimalvalori che si desiderava convertire in stringa e ci si aspettava che ci fossero molti duplicati. Sfortunatamente, se si usasse tale memorizzazione nella cache per convertire 12,3 me 12,40 m, seguiti da 12,30 me 12,4 m, questi ultimi valori restituirebbero "12,3" e "12,40" invece di "12,30" e "12,4".

Tornando alla questione in esame, c'è più di un modo sensato per confrontare gli oggetti nullable per l'uguaglianza. C # prende il punto di vista che il suo ==operatore dovrebbe rispecchiare il comportamento di Equals. VB.NET assume il punto di vista che il suo comportamento dovrebbe rispecchiare quello di alcuni altri linguaggi, poiché chiunque lo desideri Equalspotrebbe usarlo Equals. In un certo senso, la soluzione giusta sarebbe avere un costrutto "if" a tre vie e richiedere che se l'espressione condizionale restituisce un risultato a tre valori, il codice deve specificare cosa dovrebbe accadere nel nullcaso. Poiché questa non è un'opzione con le lingue così come sono, la prossima migliore alternativa è semplicemente imparare come funzionano le diverse lingue e riconoscere che non sono la stessa cosa.

Per inciso, l'operatore "Is" di Visual Basic, che manca in C, può essere utilizzato per verificare se un oggetto nullable è, in effetti, null. Sebbene ci si possa ragionevolmente chiedere se un iftest debba accettare a Boolean?, avere i normali operatori di confronto restituiti Boolean?piuttosto che Booleanquando invocati su tipi nullable è una caratteristica utile. Per inciso, in VB.NET, se si tenta di utilizzare l'operatore di uguaglianza anziché Is, si riceverà un avviso che il risultato del confronto sarà sempre Nothinge si dovrebbe usare Isse si vuole verificare se qualcosa è nullo.


Il test se una classe è null in C # viene eseguito da == null. E il test se un tipo di valore nullable ha un valore viene eseguito da .hasValue. Che utilità c'è per un Is Nothingoperatore? C # ha, isma verifica la compatibilità dei tipi. Alla luce di questi, non sono davvero sicuro di cosa stia cercando di dire il tuo ultimo paragrafo.
ErikE

@ErikE: sia vb.net che C # consentono di controllare i tipi nullable per un valore utilizzando un confronto null, sebbene entrambi i linguaggi lo considerino come zucchero sintattico per un HasValuecontrollo, almeno nei casi in cui il tipo è noto (non sono sicuro quale codice viene generato per i generici).
supercat

In generics puoi ottenere problemi complicati sui tipi nullable e sulla risoluzione dell'overload ...
ErikE

3

Potrebbe essere questo post aiutarti:

Se ricordo bene, "Niente" in VB significa "il valore predefinito". Per un tipo di valore, questo è il valore predefinito, per un tipo di riferimento, sarebbe null. Quindi, non assegnare nulla a una struttura non è affatto un problema.


3
Questo non risponde alla domanda.
David Heffernan

No, non chiarisce nulla. La domanda è tutta <>sull'operatore in VB e su come opera sui tipi nullable.
David Heffernan

2

Questa è una netta stranezza di VB.

In VB, se vuoi confrontare due tipi nullable, dovresti usare Nullable.Equals().

Nel tuo esempio, dovrebbe essere:

Dim x As System.Nullable(Of Decimal) = Nothing
Dim y As System.Nullable(Of Decimal) = Nothing

y = 5
If Not Nullable.Equals(x, y) Then
    Console.WriteLine("true")
Else
    Console.WriteLine("false")
End If

5
È "stranezza" quando non è familiare. Vedi risposta data da Pieter Geerkens.
rskar

Beh, penso anche che sia strano che VB non riproduca il comportamento di Nullable<>.Equals(). Ci si potrebbe aspettare che funzioni allo stesso modo (che è ciò che fa C #).
Matthew Watson

Le aspettative, come ciò che "ci si potrebbe aspettare", riguardano ciò che si è vissuto. C # è stato progettato tenendo presenti le aspettative degli utenti Java. Java è stato progettato tenendo conto delle aspettative degli utenti C / C ++. Nel bene e nel male, VB.NET è stato progettato pensando alle aspettative degli utenti di VB6. Altro spunto di riflessione a stackoverflow.com/questions/14837209/... e stackoverflow.com/questions/10176737/...
rskar

1
@MatthewWatson La definizione di Nullablenon esisteva nelle prime versioni di .NET, fu creata dopo che C # e VB.NET erano usciti da tempo e già determinava il loro comportamento di propagazione nullo. Sinceramente ti aspetti che il linguaggio sia stato coerente con un tipo che non sarà stato creato per diversi anni? Dal punto di vista di un programmatore VB.NET, è Nullable.Equals che non è coerente con il linguaggio, piuttosto che il contrario. (Dato che C # e VB utilizzano entrambi la stessa Nullabledefinizione, non c'era modo che fosse coerente con entrambe le lingue.)
Servy

0

Il tuo codice VB è semplicemente errato - se cambi "x <> y" in "x = y" avrai ancora "false" come risultato. Il modo più comune di esprimerlo per istanze nullable è "Not x.Equals (y)", e questo produrrà lo stesso comportamento di "x! = Y" in C #.


1
A meno che non lo xsia nothing, nel qual caso x.Equals(y)verrà generata un'eccezione.
Servizio

@Servy: ci siamo imbattuti di nuovo in questo (molti anni dopo) e ho notato che non ti ho corretto - "x.Equals (y)" non genererà un'eccezione per l'istanza di tipo nullable 'x'. I tipi nullable vengono trattati in modo diverso dal compilatore.
Dave Doknjas

In particolare, un'istanza nullable inizializzata su "null" non è realmente una variabile impostata su null, ma un'istanza System.Nullable senza alcun valore impostato.
Dave Doknjas
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.