Dlaczego istnieje różnica w sprawdzaniu wartości null względem wartości w VB.NET i C #?

110

W VB.NET dzieje się tak:

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

Ale w C # dzieje się tak:

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");
}

Dlaczego jest różnica?

blindmeis
źródło
22
to przerażające.
Mikeb
8
Uważam, że default(decimal?)zwraca 0, nie null.
Ryan Frame
7
@RyanFrame NO. Ponieważ są to typy dopuszczające wartość null , zwracanull
Soner Gönül
4
O tak ... tak ... w Ifwarunkach VB nie trzeba oceniać jako wartości logicznej ... uuuugh EDIT: Więc Nothing <> Anything = Nothingco skutkuje Ifobjęciem trasy negatyw / else.
Chris Sinclair,
13
@JMK: Null, Nothing i Empty są w rzeczywistości nieznacznie różne. Gdyby wszystkie były takie same, nie potrzebowałbyś trzech z nich.
Eric Lippert

Odpowiedzi:

88

VB.NET i C # .NET to różne języki, stworzone przez różne zespoły, które przyjęły różne założenia dotyczące użycia; w tym przypadku semantyka porównania NULL.

Osobiście preferuję semantykę VB.NET, która w istocie nadaje NULL semantykę „Jeszcze nie wiem”. Następnie porównanie 5 do „Jeszcze nie wiem”. jest naturalnie „jeszcze nie wiem”; tj. NULL. Ma to dodatkową zaletę polegającą na odzwierciedlaniu zachowania wartości NULL w (większości, jeśli nie we wszystkich) bazach danych SQL. Jest to również bardziej standardowa (niż w języku C #) interpretacja logiki trójwartościowej, jak wyjaśniono tutaj .

Zespół C # przyjął różne założenia dotyczące tego, co oznacza wartość NULL, co spowodowało pokazaną różnicę w zachowaniu. Eric Lippert napisał bloga o znaczeniu NULL w C # . Per Eric Lippert: „Pisałem też o semantyce wartości null w VB / VBScript i JScript tu i tutaj ”.

W każdym środowisku, w którym możliwe są wartości NULL, nie można już uznawać, że Prawo Wyłączonego Środka (tj. Że A lub ~ A jest tautologicznie prawdziwe) nie może już być oparte na prawach.

Aktualizacja:

A bool(w przeciwieństwie do a bool?) może przyjmować tylko wartości PRAWDA i FAŁSZ. Jednak implementacja języka NULL musi zdecydować, w jaki sposób NULL propaguje się przez wyrażenia. W VB wyrażenia 5=nulli 5<>nullOBIE zwracają fałsz. W języku C # z porównywalnych wyrażeń 5==nulli 5!=nulltylko drugie pierwsze [zaktualizowane 2014-03-02 - PG] zwraca wartość false. Jednak w KAŻDYM środowisku, które obsługuje wartość null, na programiście spoczywa obowiązek znajomości tabel prawdy i propagacji wartości null używanych przez ten język.

Aktualizacja

Artykuły na blogu Erica Lipperta (wymienione w jego komentarzach poniżej) na temat semantyki są teraz dostępne pod adresem:

Pieter Geerkens
źródło
4
Dzięki za link. O semantyce wartości null w VB / VBScript i JScript pisałem również tutaj: blogs.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx oraz tutaj: blogs.msdn.com/b/ericlippert/ archive / 2003/10/01 / 53128.aspx
Eric Lippert
27
A do Twojej wiadomości decyzja o uczynieniu C # niekompatybilnym z VB w ten sposób była kontrowersyjna. Nie byłem wtedy w zespole zajmującym się projektowaniem języków, ale ilość dyskusji, która doprowadziła do tej decyzji, była znaczna.
Eric Lippert
2
@ BlueRaja-DannyPflughoeft W C # boolnie może mieć 3 wartości, tylko dwie. To bool?może mieć trzy wartości. operator ==i operator !=oba zwracają bool, nie bool?, niezależnie od typu operandów. Ponadto ifoświadczenie może akceptować tylko a bool, a nie bool?.
Servy
1
W C # wyrażenia 5=nulli 5<>nullnie są prawidłowe. I 5 == nullczy 5 != nullna pewno jest to druga, która powraca false?
Ben Voigt,
1
@BenVoigt: Dziękuję. Wszystkie te głosy pozytywne i jako pierwszy zauważysz tę literówkę. ;-)
Pieter Geerkens
37

Ponieważ x <> yzwraca Nothingzamiast true. Po prostu nie jest zdefiniowany, ponieważ xnie jest zdefiniowany. (podobnie do SQL null).

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

Musisz także porównać wartość a Nullable(Of Decimal)tylko wtedy, gdy ma wartość.

Więc VB.NET powyżej porównuje się podobnie do tego (który wygląda mniej niepoprawnie):

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

Specyfikacja języka VB.NET :

7.1.1 Typy wartości dopuszczających wartość null ... Typ wartości dopuszczającej wartość null może zawierać te same wartości, co wersja typu, która nie dopuszcza wartości null, a także wartość null. W związku z tym w przypadku typu wartości dopuszczającej wartość null przypisanie Nothing do zmiennej typu ustawia wartość zmiennej na wartość null, a nie na wartość zerową typu wartości.

Na przykład:

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) '
Tim Schmelter
źródło
16
„VB.NET Nothing <> C # null” czy zwraca wartość true dla C # i false dla VB.Net? Żartuję :-p
ken2k
17

Spójrz na wygenerowany CIL (przekonwertowałem oba na C #):

DO#:

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");
    }
}

Zobaczysz, że porównanie w Visual Basic zwraca wartość Nullable <bool> (nie bool, false lub true!). A undefined przekonwertowany na bool jest fałszywy.

Nothingw porównaniu z tym, co jest zawsze Nothing, a nie fałszem w Visual Basic (to jest to samo, co w SQL).

nothrow
źródło
Po co odpowiadać na to pytanie metodą prób i błędów? Powinno to być możliwe na podstawie specyfikacji języka.
David Heffernan
3
@DavidHeffernan, ponieważ to pokazuje różnicę w języku, która jest dość jednoznaczna.
nothrow
2
@Yossarian Uważasz, że specyfikacje językowe są niejednoznaczne w tej kwestii. Nie zgadzam się. IL to szczegół implementacji, który może ulec zmianie; specyfikacje nie są.
Servy
2
@DavidHeffernan: Podoba mi się twoja postawa i zachęcam do spróbowania. Specyfikacja języka VB może być czasami trudna do przeanalizowania. Lucian udoskonala to już od kilku lat, ale wciąż może być dość trudno odgadnąć dokładne znaczenie tego rodzaju przypadków narożnych. Proponuję zdobyć kopię specyfikacji, przeprowadzić pewne badania i zgłosić swoje odkrycia.
Eric Lippert
2
@Yossarian Wyniki wykonania podanego kodu IL nie mogą ulec zmianie, ale dostarczony kod C # / VB zostanie wkompilowany w pokazany kod IL może ulec zmianie (o ile zachowanie tego kodu IL jest również zgodne z definicją specyfikacji języka).
Servy
6

Zaobserwowany tu problem jest szczególnym przypadkiem bardziej ogólnego problemu, polegającego na tym, że liczba różnych definicji równości, które mogą być przydatne przynajmniej w niektórych okolicznościach, przekracza liczbę powszechnie dostępnych sposobów ich wyrażenia. W niektórych przypadkach ten problem pogarsza niefortunne przekonanie, że mylące jest posiadanie różnych metod testowania równości, które dają różne wyniki, a takiego zamieszania można uniknąć, jeśli różne formy równości dają takie same wyniki, kiedy tylko jest to możliwe.

W rzeczywistości podstawową przyczyną nieporozumień jest błędne przekonanie, że należy oczekiwać, że różne formy testowania równości i nierówności dadzą ten sam wynik, niezależnie od faktu, że różne semantyki są przydatne w różnych okolicznościach. Na przykład, z arytmetycznego punktu widzenia, warto mieć możliwość porównania, Decimalktóre różnią się tylko liczbą końcowych zer, jako równe. Podobnie dla doublewartości takich jak dodatnie zero i ujemne zero. Z drugiej strony, z punktu widzenia buforowania lub internowania, taka semantyka może być śmiertelna. Załóżmy na przykład, że ktoś ma Dictionary<Decimal, String>taki, który myDict[someDecimal]powinien być równy someDecimal.ToString(). Taki przedmiot wydawałby się rozsądny, gdyby ktoś miał ich wieleDecimalwartości, które ktoś chciał przekonwertować na łańcuch i spodziewał się wielu duplikatów. Niestety, gdyby wykorzystano takie buforowanie do konwersji 12,3 mi 12,40 m, a następnie 12,30 mi 12,4 m, te ostatnie wartości dałyby „12,3” i „12,40” zamiast „12,30” i „12,4”.

Wracając do omawianej sprawy, istnieje więcej niż jeden rozsądny sposób porównywania obiektów dopuszczających wartość zerową pod kątem równości. C # przyjmuje stanowisko, że jego ==operator powinien odzwierciedlać zachowanie Equals. VB.NET stoi na stanowisku, że jego zachowanie powinno odzwierciedlać zachowanie niektórych innych języków, ponieważ każdy, kto chce, Equalsmoże użyć tego zachowania Equals. W pewnym sensie właściwym rozwiązaniem byłoby posiadanie trójczynnikowej konstrukcji „jeśli” i wymaganie, aby jeśli wyrażenie warunkowe zwracało trójwartościowy wynik, kod musiał określać, co powinno się wydarzyć w nullprzypadku. Ponieważ nie jest to możliwe w przypadku języków, którymi są, kolejną najlepszą alternatywą jest po prostu nauczenie się, jak działają różne języki i uznanie, że nie są one tym samym.

Nawiasem mówiąc, operator „Is” języka Visual Basic, którego brakuje w C, może służyć do testowania, czy obiekt dopuszczający wartość null jest w rzeczywistości pusty. Chociaż można rozsądnie zastanawiać się, czy iftest powinien akceptować a Boolean?, użyteczną funkcją jest zwracanie normalnych operatorów porównania Boolean?zamiast Booleanwywoływania ich na typach dopuszczających wartość null. Nawiasem mówiąc, w VB.NET, jeśli ktoś spróbuje użyć operatora równości zamiast Is, otrzyma ostrzeżenie, że wynik porównania zawsze będzie Nothingi należy go użyć, Isjeśli chce się sprawdzić, czy coś jest zerowe.

supercat
źródło
Testowanie, czy klasa jest null w C #, jest wykonywane przez == null. Testowanie, czy typ wartości dopuszczającej wartość null ma wartość, jest wykonywane przez .hasValue. Jaki jest pożytek z Is Nothingoperatora? C # ma, isale testuje zgodność typu. W świetle powyższego naprawdę nie jestem pewien, co próbuje powiedzieć twój ostatni akapit.
ErikE
@ErikE: Zarówno vb.net, jak i C # pozwalają na sprawdzanie wartości dopuszczających wartość null za pomocą porównania z null, chociaż oba języki traktują to jako cukier składniowy do HasValuesprawdzenia, przynajmniej w przypadkach, gdy typ jest znany (nie jestem pewien jaki kod jest generowany dla typów generycznych).
supercat
W typach generycznych można uzyskać skomplikowane problemy związane z typami dopuszczającymi wartość
zerową
3

Może ten post ci pomoże:

Jeśli dobrze pamiętam, „nic” w VB oznacza „wartość domyślna”. W przypadku typu wartości jest to wartość domyślna, w przypadku typu referencyjnego byłaby to null. Zatem przypisanie niczego do struktury nie stanowi żadnego problemu.

evgenyl
źródło
3
To nie odpowiada na pytanie.
David Heffernan
Nie, to niczego nie wyjaśnia. Pytanie dotyczy <>operatora w języku VB i tego, jak działa on na typach dopuszczających wartość null.
David Heffernan
2

To zdecydowanie dziwaczność VB.

W języku VB, jeśli chcesz porównać dwa typy dopuszczające wartość null, powinieneś użyć Nullable.Equals().

W Twoim przykładzie powinno to być:

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
Matthew Watson
źródło
5
To „dziwność”, kiedy nie jest znajoma. Zobacz odpowiedź udzieloną przez Pietera Geerkensa.
rskar
Cóż, wydaje mi się również dziwne, że VB nie odtwarza zachowania Nullable<>.Equals(). Można by się spodziewać, że będzie działać w ten sam sposób (co robi C #).
Matthew Watson
Oczekiwania, podobnie jak to, czego „można się spodziewać”, dotyczą tego, czego się doświadczył. C # został zaprojektowany z myślą o oczekiwaniach użytkowników języka Java. Java została zaprojektowana z myślą o oczekiwaniach użytkowników C / C ++. Na dobre lub na złe, VB.NET został zaprojektowany z myślą o oczekiwaniach użytkowników VB6. Więcej do przemyślenia na stackoverflow.com/questions/14837209/ ... i stackoverflow.com/questions/10176737/…
rskar
1
@MatthewWatson Definicja Nullablenie istniała w pierwszych wersjach .NET, została utworzona po tym, jak C # i VB.NET były niedostępne przez jakiś czas i już określały ich zachowanie w zakresie propagacji zerowej. Czy naprawdę oczekujesz, że język będzie spójny z typem, który nie powstanie przez kilka lat? Z punktu widzenia programisty VB.NET jest to Nullable.Equals, które nie są zgodne z językiem, a nie odwrotnie. (Biorąc pod uwagę, że C # i VB używają tej samej Nullabledefinicji, nie było sposobu, aby była spójna z obydwoma językami.)
Servy
0

Twój kod VB jest po prostu nieprawidłowy - jeśli zmienisz „x <> y” na „x = y”, nadal będziesz miał jako wynik „fałsz”. Najczęstszym sposobem wyrażenia this dla wystąpień dopuszczających wartość null jest „Not x.Equals (y)”, co daje takie samo zachowanie jak „x! = Y” w C #.

Dave Doknjas
źródło
1
Chyba że xtak nothing, w takim przypadku x.Equals(y)zgłosi wyjątek.
Servy
@Servy: Natknąłem się na to ponownie (wiele lat później) i zauważyłem, że cię nie poprawiłem - „x.Equals (y)” nie zgłosi wyjątku dla instancji typu „x” dopuszczającej wartość null. Typy dopuszczające wartość null są traktowane inaczej przez kompilator.
Dave Doknjas
W szczególności instancja dopuszczająca wartość null zainicjowana jako „null” nie jest w rzeczywistości zmienną o wartości null, ale instancją System.Nullable bez ustawionej wartości.
Dave Doknjas,