Чому в VB.NET і C # є різниця у тому, щоб перевірити нуль на значення?


110

У VB.NET це відбувається:

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

Але в C # це відбувається:

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

Чому є різниця?


22
це жахливо
Майк

8
Я вважаю default(decimal?), повертає 0, ні null.
Райан Кадр

7
@RyanFrame НІ. Оскільки це незмінні типи , він повертаєтьсяnull
Soner Gönül

4
О так ... так ... в VB Ifумовні умови не вимагають оцінювати як булеві ... uuuugh EDIT: Отже, Nothing <> Anything = Nothingщо призводить до Ifприйняття негативного / іншого маршруту.
Кріс Сінклер

13
@JMK: Null, Nothing та Empty - насправді всі тонко різні. Якби вони були однакові, то вам три не знадобилося б.
Ерік Ліпперт

Відповіді:


88

VB.NET і C # .NET - це різні мови, побудовані різними командами, які зробили різні припущення щодо використання; у цьому випадку семантика порівняння NULL.

Мої особисті переваги - семантика VB.NET, яка по суті надає NULL семантику "я ще не знаю". Тоді порівняння 5 із "я ще не знаю". природно "я ще не знаю"; тобто NULL. Це має додаткову перевагу у відображенні поведінки NULL у (більшості, якщо не у всіх) базах даних SQL. Це також більш стандартне (ніж C # 's) тлумачення тризначної логіки, як пояснено тут .

Команда C # зробила різні припущення щодо того, що означає NULL, внаслідок чого ви проявляєте різницю в поведінці. Ерік Ліпперт написав блог про значення NULL в C # . Пер Ерік Ліпперт: "Я також писав про семантику нулів у VB / VBScript та JScript тут і тут ".

У будь-якому середовищі, в якому можливі значення NULL, важливо визнати, що на Закон виключеного середини (тобто, що A або ~ A є тавтологічно правдивим) більше не можна покладатися на них.

Оновлення:

A bool(на відміну від a bool?) може приймати лише значення TRUE і FALSE. Однак мовна реалізація NULL повинна визначати, як NULL поширюється за допомогою виразів. У VB вирази 5=nullта 5<>nullBOTH повертаються хибними. У C # порівняних виразів 5==nullі 5!=nullлише другий перший [оновлений 2014-03-02 - PG] повертає помилкові. Однак у будь-якому середовищі, яке підтримує null, програміст повинен знати таблиці істинності та поширення нуля, використовувані цією мовою.

Оновлення

Статті в блозі Еріка Ліпперта (згадані в його коментарях нижче) про семантику зараз:


4
Дякуємо за посилання. Я також писав про семантику нулів у VB / VBScript та JScript тут: blogs.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx і тут: blogs.msdn.com/b/ericlippert/ архів / 2003/10/01 / 53128.aspx
Ерік Ліпперт

27
І FYI рішення зробити C # несумісним з VB таким чином було суперечливим. У той час я не був у команді з мовної розробки, але кількість дискусій, які обговорили це рішення, була значною.
Ерік Ліпперт

2
@ BlueRaja-DannyPflughoeft У C # boolне може бути 3 значення, лише два. Це bool?може мати три значення. operator ==і operator !=обидва повертаються bool, ні bool?, незалежно від типу операндів. Крім того, ifзаяву можна приймати лише a bool, а не a bool?.
Сервіс

1
У C # вираження 5=nullі 5<>nullне є дійсними. І, 5 == nullі 5 != nullви впевнені, що повертається другий false?
Ben Voigt

1
@BenVoigt: Дякую Усі голосуючі, і ви вперше помітили цю помилку. ;-)
Пітер Геркенс

37

Тому що x <> yповертається Nothingзамість true. Так просто не визначеноx не визначений. (схоже на SQL null).

Примітка: VB.NET Nothing<> C # null .

Ви також повинні порівняти значення а Nullable(Of Decimal) єдиного, якщо воно має значення.

Отже, VB.NET вище порівнює подібне до цього (що виглядає менш некоректно):

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

Специфікація мови VB.NET :

7.1.1 Типи нульового значення ... Тип нульового значення може містити ті самі значення, що і ненульована версія типу, а також нульове значення. Таким чином, для типу нульового значення, присвоєння нічого перемінному типу, встановлює значення змінної нульове значення, а не нульове значення типу значення.

Наприклад:

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" повертає істину для C #, а false - для VB.Net?
Жартую

17

Подивіться на створений CIL (я перетворив обидва на 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");
    }
}

Ви побачите, що порівняння у Visual Basic повертає Nullable <bool> (не bool, false або true!). І невизначений перетворений на bool - помилковий.

Nothingпорівняно з тим, що завжди є Nothing, не помилковим у Visual Basic (це те саме, що і в SQL).


Чому відповідати на питання спроб і помилок? Потрібно зробити це з мовних специфікацій.
Девід Геффернан

3
@DavidHeffernan, оскільки це показує різницю в мові, яка є досить однозначною.
nothrow

2
@Yossarian Ви вважаєте, що мовні характеристики у цьому питанні неоднозначні. Я не погоджуюсь. ІР - це деталь реалізації, що може бути змінена; характеристики - ні.
Сервіс

2
@DavidHeffernan: Мені подобається ваше ставлення і закликаю вас спробувати. Специфікація мови VB часом може бути складною для розбору. Лучан уже кілька років удосконалює це, але все ще може бути досить складно вияснити точні значення таких різновидів кутових справ. Я пропоную вам отримати копію специфікації, провести деякі дослідження та повідомити про свої висновки.
Ерік Ліпперт

2
@Yossarian результати виконання коду IL ви надали не підлягає зміні, але що код C # / VB при умови , буде скомпільовано в код IL ви показали , це може бути змінений ( до тих пір , як поведінка , що IL є також вказується на визначення мовних специфікацій).
Сервіс

6

Проблема, яка спостерігається тут, є окремим випадком більш загальної проблеми, яка полягає в тому, що кількість різних визначень рівності, які можуть бути корисні принаймні в деяких обставинах, перевищує кількість загальнодоступних засобів їх вираження. Ця проблема в деяких випадках погіршується прикрою думкою, що заплутаність у тому, що різні засоби тестування рівності дають різні результати, і подібної плутанини можна уникнути, якщо різні форми рівності дають однакові результати, коли це можливо.

Насправді основною причиною плутанини є помилкова думка, що від різних форм тестування рівності та нерівності слід очікувати того ж результату, незважаючи на те, що різні семантики корисні в різних умовах. Наприклад, з арифметичної точки зору корисно мати можливість мати такі, Decimalякі відрізняються лише кількістю зворотних нулів порівняти як рівні. Аналогічно для doubleтаких значень, як позитивний нуль і від’ємний нуль. З іншого боку, з точки зору кешування чи інтернування така семантика може бути смертельною. Припустимо, наприклад, у одного було Dictionary<Decimal, String>таке, що myDict[someDecimal]повинно дорівнюватиsomeDecimal.ToString() . Такий об’єкт здавався б розумним, якби їх було багатоDecimalзначення, які хотіли перетворити на рядок і очікували, що там буде багато дублікатів. На жаль, якщо використовувати таке кешування для перетворення 12,3 м та 12,40 м, а потім 12,30 м та 12,4 м, останні значення отримають "12,3" та "12,40" замість "12,30" та "12,4".

Повертаючись до розглянутої справи, існує більш ніж один розумний спосіб порівняння зведених об’єктів для рівності. C # займає точку зору, на яку його ==оператор повинен відображати поведінку Equals. VB.NET вважає, що його поведінка повинна відображати поведінку деяких інших мов, оскільки кожен, хто хоче Equalsповедінку, може використовувати Equals. У певному сенсі правильним рішенням було б мати тристоронню конструкцію "якщо", і вимагати, якщо умовний вираз поверне тризначний результат, код повинен вказати, що має відбуватися у nullвипадку. Оскільки це не варіант із мовами, якими вони є, наступна найкраща альтернатива - просто дізнатися, як працюють різні мови, і визнати, що вони не однакові.

Між іншим, оператор "Є" Visual Basic, якого не вистачає на C, може бути використаний для перевірки того, чи є нульовий об'єкт насправді нульовим. Хоча можна обгрунтовано поставити під сумнів, чи ifповинен тест приймати a Boolean?, тим, що нормальні оператори порівняння повертаються, Boolean?а не Booleanколи вони викликаються на нульові типи, є корисною функцією. До речі, у VB.NET, якщо намагається скористатись оператором рівності, а не Is, отримуватиме попередження про те, що результат порівняння завжди буде Nothing, і його слід використовувати, Isякщо потрібно перевірити, чи щось недійсне.


Тестування того, чи є клас нульовим в C #, робиться == null. І перевіряється, чи має тип нульового значення значення .hasValue. Яке використання Is Nothingоператору? У C # є, isале він перевіряє сумісність типів. Зважаючи на це, я дійсно не впевнений, що намагається сказати ваш останній абзац.
ErikE

@ErikE: І vb.net, і C # дозволяють перевірити значення змінних типів за допомогою порівняння null, хоча обидві мови вважають це синтаксичним цукром для HasValueперевірки, принаймні у випадках, коли тип відомий (я не впевнений який код генерується для дженерики).
supercat

У дженеріках ви можете отримати складні проблеми навколо змінних типів і вирішення перевантаження ...
ErikE

3

Можливо, ця публікація допоможе вам:

Якщо я правильно пам'ятаю, "Ніщо" у VB означає "значення за замовчуванням". Для типу значення це значення за замовчуванням, для референтного типу, що було б нульовим. Таким чином, привласнення нічого структурі - це зовсім не проблема.


3
Це не відповідає на запитання.
Девід Геффернан

Ні, це нічого не уточнює. Питання в тому, що стосується <>оператора в VB та того, як він працює на змінних типах.
Девід Геффернан

2

Це певна дивність ВБ.

У VB, якщо ви хочете порівняти два мінливі типи, вам слід скористатися Nullable.Equals().

У вашому прикладі повинно бути:

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
Це "дивацтво", коли воно не знайоме. Дивіться відповідь, надану Пітером Геркенсом.
rskar

Ну, я також думаю, що дивно, що VB не відтворює поведінку Nullable<>.Equals(). Можна очікувати, що він працює так само (як це робить C #).
Меттью Уотсон

Очікування, як і в тому, що "можна очікувати", - це те, що пережито. C # був розроблений з урахуванням очікувань користувачів Java. Java була розроблена з урахуванням очікувань користувачів C / C ++. На краще чи гірше, VB.NET був розроблений з урахуванням очікувань користувачів VB6. Більше приводів для роздумів на stackoverflow.com/questions/14837209 / ... і stackoverflow.com/questions/10176737 / ...
rskar

1
@MatthewWatson Визначення Nullableне існувало в перших версіях .NET, воно було створене після того, як C # і VB.NET були вимкнені деякий час і вже визначили їх нульову поведінку розповсюдження. Чи чесно ви очікуєте, що мова відповідатиме типу, який не буде створений протягом кількох років? З точки зору програміста VB.NET, це Nullable.Equals, що не відповідає мові, а не навпаки. (З огляду на те, що обидва C # і VB використовують одне і те ж Nullableвизначення, не було можливості узгоджувати обидві мови.)
Сервіс

0

Ваш код VB просто невірний - якщо ви зміните "x <> y" на "x = y", у результаті все одно з'явиться "false". Найпоширенішим способом вираження цього для змінних екземплярів є "Not x.Equals (y)", і це призведе до такої ж поведінки, що і "x! = Y" у C #.


1
Якщо тільки xнемає nothing, в такому випадку x.Equals(y)викине виняток.
Сервіс

@Servy: Натрапили на це знову (через багато років) і помітили, що я вас не виправдав - "x.Equals (y)" не викине виключення для примірника типу "x". Зменшувані типи компілятором трактуються по-різному.
Дейв Докняс

Зокрема, нульовий екземпляр, ініціалізований на 'null', насправді не є змінною, встановленою для null, а екземпляром System.Nullable без встановленого значення.
Дейв Докняс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.