Почему есть разница в проверке нулевого значения в VB.NET и C #?

108

В 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

Но в С# это происходит:

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 .
Показать ещё 12 комментариев
Теги:
null
.net-4.0

7 ответов

85
Лучший ответ

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

Мои личные предпочтения относятся к семантике VB.NET, которая по сути дает NULL семантику "Я еще не знаю". Тогда сравнение 5 с "Я еще не знаю". естественно, "я еще не знаю"; т.е. NULL. Это имеет дополнительное преимущество для зеркалирования поведения NULL в (большинство, если не все) баз данных SQL. Это также более стандартная (чем С#) интерпретация трехзначной логики, как описано здесь.

Команда С# сделала разные предположения о том, что означает NULL, что приводит к различию в поведении, которое вы показываете. Эрик Липперт написал блог о значении NULL в С#. Per Eric Lippert: "Я также написал о семантике нулей в VB/VBScript и JScript здесь и здесь".

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

Update:

A bool (в отличие от bool?) может принимать значения TRUE и FALSE. Однако языковая реализация NULL должна определять, как NULL распространяется через выражения. В VB выражения 5=null и 5<>null BOTH возвращают false. В С# из сопоставимых выражений 5==null и 5!=null только second сначала [обновлено 2014-03-02 - PG] возвращает false. Однако в ЛЮБОМ окружении, поддерживающем нуль, программисту необходимо знать таблицы истинности и нулевое распространение, используемое этим языком.

  • 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
    И к вашему сведению, решение сделать C # несовместимым с VB таким образом было спорным. В то время я не входил в группу по разработке языков, но количество дебатов, которые привели к этому решению, было значительным.
Показать ещё 7 комментариев
33

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

Примечание: VB.NET Nothing < > С# 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 Типы допустимых значений... Тип с нулевым значением может содержать те же значения, что и значение, не равное нулю версию типа, а также нулевое значение. Таким образом, для нулевых тип значения, присваивание Nothing переменной типа устанавливает значение от переменной до нулевого значения, а не нулевого значения значения тип.

Например:

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" возвращает true для C # и false для VB.Net? Шучу :-p
  • 1
    @ ken2k Конечно нет, в VB.NET он Nothing не возвращает. :-)
17

Посмотрите на сгенерированный CIL (я преобразовал оба в С#):

С#:

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, а не false в Visual Basic (это то же самое, что и в SQL).

  • 0
    Зачем отвечать на вопрос методом проб и ошибок? Должно быть возможно сделать это из спецификации языка.
  • 3
    @DavidHeffernan, потому что это показывает разницу в языке, которая довольно однозначна.
Показать ещё 6 комментариев
6

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

В действительности основной причиной путаницы является ошибочное убеждение в том, что различные формы тестирования равенства и неравенства должны давать одинаковый результат, несмотря на то, что различные семантики полезны в разных обстоятельствах. Например, с арифметической точки зрения полезно иметь Decimal, которые отличаются только числом конечных нулей, сравниваемых как равные. Аналогично для double значений, таких как положительный нуль и отрицательный нуль. С другой стороны, с точки зрения кеширования или интернирования, такая семантика может быть смертельной. Предположим, например, что a Dictionary<Decimal, String> такое, что myDict[someDecimal] должно равняться someDecimal.ToString(). Такой объект представляется разумным, если бы у него было много значений Decimal, которые нужно было преобразовать в строку, и ожидалось, что их будет много дубликатов. К сожалению, если использовать такое кэширование для преобразования 12.3 m и 12.40 m, за которым следуют 12.30 & middot; m и 12.4 m, последние значения будут давать "12.3" и "12.40" вместо "12.30" и "12,4".

Возвращаясь к рассматриваемому вопросу, существует более чем один разумный способ сравнения объектов с нулевым значением для равенства. С# считает, что оператор == должен отражать поведение Equals. VB.NET считает, что его поведение должно отражать поведение других языков, поскольку любой, кто хочет поведение Equals, может использовать Equals, В некотором смысле правильным решением было бы иметь трехстороннюю конструкцию "if" и требовать, чтобы, если условное выражение возвращает трехзначный результат, код должен указать, что должно произойти в случае null. Поскольку это не вариант с языками, как они есть, следующей лучшей альтернативой является просто узнать, как работают разные языки, и признать, что они не совпадают.

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

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

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

Если я правильно помню, "Nothing" в VB означает "значение по умолчанию". Для типа значения это значение по умолчанию для ссылочного типа должно быть нулевым. Таким образом, не присваивая ничего структуре, это не проблема.

  • 3
    Это не отвечает на вопрос.
  • 0
    @David_Heffernan Я думал, это достаточно ясно.
Показать ещё 1 комментарий
2

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

В 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
    Это "странность", когда это не знакомо. Смотрите ответ, данный Питером Гиркенсом.
  • 0
    Я также считаю странным, что VB не воспроизводит поведение Nullable<>.Equals() . Можно ожидать, что он будет работать так же (как это делает C #).
Показать ещё 2 комментария
0

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

  • 1
    Если x является nothing , в этом случае x.Equals(y) выдаст исключение.
  • 0
    @Servy: снова наткнулся на это (много лет спустя) и заметил, что я вас не исправил - «x.Equals (y)» не сгенерирует исключение для экземпляра обнуляемого типа «x». Обнуляемые типы обрабатываются компилятором по-разному.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню