Почему существует разница в проверке нуля на значение в 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");
}

Почему есть разница?

blindmeis
источник
22
это ужасно.
Mikeb
8
Я считаю, default(decimal?)что возвращается 0, а не null.
Ryan Frame
7
@RyanFrame НЕТ. Поскольку это типы , допускающие значение NULL , он возвращаетnull
Сонер Генюль
4
Ах да ... верно ... в Ifусловных выражениях VB не требуется оценивать как логическое ... uuuugh EDIT: Итак, Nothing <> Anything = Nothingчто приводит к выбору Ifмаршрута отрицательного / else.
Крис Синклер,
13
@JMK: Null, Nothing и Empty на самом деле немного отличаются. Если бы все они были одинаковыми, вам бы не понадобились три из них.
Эрик Липперт

Ответы:

88

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

Лично я предпочитаю семантику VB.NET, которая, по сути, дает NULL семантику «Я еще не знаю». Затем сравнение 5 с «Еще не знаю». естественно «я еще не знаю»; т.е. NULL. Это имеет дополнительное преимущество в виде зеркального отображения поведения NULL в (в большинстве, если не во всех) базах данных SQL. Это также более стандартная (чем в C #) интерпретация трехзначной логики, как объясняется здесь .

Команда C # сделала разные предположения о том, что означает NULL, что привело к разнице в поведении, которую вы показываете. Эрик Липперт написал в блоге о значении NULL в C # . Пер Эрик Липперт: «Я также писал о семантике пустых значений в VB / VBScript и JScript здесь и здесь ».

В любой среде, в которой возможны значения NULL, важно признать, что на закон исключенного среднего (т. Е. Что A или ~ A тавтологически истинны) больше нельзя полагаться.

Обновить:

A bool(в отличие от a bool?) может принимать только значения TRUE и FALSE. Однако языковая реализация NULL должна решить, как NULL распространяется через выражения. В VB выражения 5=nullи 5<>nullОБА возвращают ложь. В 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/ архив / 01.10.2003 / 53128.aspx
Эрик Липперт
27
И, к вашему сведению, решение сделать C # несовместимым с VB таким образом было спорным. В то время я не входил в команду разработчиков языков, но это решение вызвало немало споров.
Эрик Липперт
2
@ BlueRaja-DannyPflughoeft В C # boolне может быть трех значений, только два. Это bool?может иметь три значения. operator ==и operator !=оба возвращаются bool, нет bool?, независимо от типа операндов. Кроме того, ifоператор может принимать только a bool, но не a bool?.
Servy
1
В C # выражения 5=nullи 5<>nullнедействительны. И 5 == nullи 5 != null, вы уверены , что это второе , что возвращается false?
Ben Voigt
1
@BenVoigt: Спасибо. Все эти голоса за, и вы первый, кто заметил эту опечатку. ;-)
Питер Геркенс 02
37

Потому что x <> yвозвращает Nothingвместо true. Он просто не определен, поскольку xне определен. (аналогично SQL null).

Примечание: VB.NET Nothing<> C # null.

Вы также должны сравнивать значение a, 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 Типы значений, допускающие значение NULL ... Тип значения, допускающий значение NULL, может содержать те же значения, что и версия типа, не допускающая значения NULL, а также значение NULL. Таким образом, для типа значения, допускающего значение NULL, присвоение Nothing переменной этого типа устанавливает значение переменной равным NULL, а не нулевому значению типа значения.

Например:

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 # и ложь для VB.Net? Шучу :-p
ken2k
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!). А undefined, преобразованный в bool, является ложным.

Nothingпо сравнению с тем, что всегда Nothing, а не ложью в Visual Basic (это то же самое, что и в SQL).

nothrow
источник
Зачем отвечать на вопрос методом проб и ошибок? Должно быть возможно сделать это из языковых спецификаций.
Дэвид Хеффернан
3
@DavidHeffernan, потому что это однозначно показывает разницу в языке.
nothrow
2
@Yossarian Вы думаете, что спецификации языка неоднозначны по этому вопросу. Я не согласен. IL - это деталь реализации, которая может изменяться; спецификации нет.
Servy
2
@DavidHeffernan: Мне нравится ваше отношение, и я призываю вас попробовать. Спецификацию языка VB иногда бывает сложно проанализировать. Лучан улучшал его уже несколько лет, но все еще может быть довольно сложно выяснить точное значение таких угловых случаев. Я предлагаю вам получить копию спецификации, провести небольшое исследование и сообщить о своих выводах.
Эрик Липперт
2
@Yossarian Результаты выполнения предоставленного вами кода IL не подлежат изменению, но то, что предоставленный код C # / VB будет скомпилирован в показанный вами код IL, может быть изменен (при условии, что поведение этого IL также соответствует определению спецификации языка).
Servy
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. В некотором смысле правильным решением было бы иметь трехстороннюю конструкцию «if» и требовать, чтобы, если условное выражение возвращает трехзначный результат, код должен указывать, что должно происходить в nullслучае. Поскольку это не вариант с языками как они есть, следующая лучшая альтернатива - просто изучить, как работают разные языки, и признать, что они не совпадают.

Между прочим, оператор «Is» в Visual Basic, которого нет в C, можно использовать для проверки того, действительно ли объект, допускающий значение NULL, равен NULL. Хотя можно резонно задаться вопросом, ifдолжен ли тест принимать a Boolean?, наличие обычных операторов сравнения, возвращаемых Boolean?вместо того, чтобы Booleanвызывать их для типов, допускающих значение NULL, является полезной функцией. Между прочим, в VB.NET, если кто-то попытается использовать оператор равенства, а не Is, вы получите предупреждение о том, что результат сравнения всегда будет Nothing, и его следует использовать, Isесли кто-то хочет проверить, является ли что-то нулевым.

Supercat
источник
Проверка того, является ли класс нулевым в C #, выполняется с помощью == null. И проверка того, имеет ли тип значения, допускающий значение NULL, значение выполняется с помощью .hasValue. Какая польза от Is Nothingоператора? В C # есть, isно он проверяет совместимость типов. В свете этого я действительно не уверен, что пытается сказать ваш последний абзац.
ErikE
@ErikE: как vb.net, так и C # позволяют проверять значения обнуляемых типов на предмет значения с использованием сравнения null, хотя оба языка рассматривают это как синтаксический сахар для HasValueпроверки, по крайней мере, в тех случаях, когда тип известен (я не уверен какой код генерируется для дженериков).
supercat
В дженериках вы можете получить сложные проблемы с типами, допускающими значение NULL, и разрешением перегрузки ...
ErikE
3

Может быть этот пост вам поможет:

Если я правильно помню, «Ничего» в VB означает «значение по умолчанию». Для типа значения это значение по умолчанию, для ссылочного типа это будет null. Таким образом, ничего не присваивать структуре - это вообще не проблема.

евгенил
источник
3
Это не отвечает на вопрос.
Дэвид Хеффернан
Нет, это ничего не проясняет. Вопрос в самом <>операторе в VB и в том, как он работает с типами, допускающими значение NULL.
Дэвид Хеффернан
2

Это определенная странность VB.

В VB, если вы хотите сравнить два типа, допускающие значение NULL, вы должны использовать 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определение, у него не было возможности согласовать его с обоими языками.)
Servy
0

Ваш код VB просто неверен - если вы измените «x <> y» на «x = y», в результате вы все равно получите «false». Наиболее распространенный способ выражения this для экземпляров, допускающих значение NULL, - «Not x.Equals (y)», и это приведет к тому же поведению, что и «x! = Y» в C #.

Дэйв Докняс
источник
1
Если xнет nothing, в этом случае x.Equals(y)будет сгенерировано исключение.
Servy
@Servy: снова наткнулся на это (много лет спустя) и заметил, что я вас не исправил - «x.Equals (y)» не вызовет исключения для экземпляра типа «x», допускающего значение NULL. Типы, допускающие значение NULL, обрабатываются компилятором по-разному.
Dave Doknjas
В частности, обнуляемый экземпляр, инициализированный как 'null', на самом деле не является переменной, для которой установлено значение null, а является экземпляром System.Nullable без установленного значения.
Дэйв Докняс