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

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

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

Ответ 1

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. Однако в ЛЮБОМ окружении, поддерживающем нуль, программисту необходимо знать таблицы истинности и нулевое распространение, используемое этим языком.

Ответ 2

Потому что 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) '

Ответ 3

Посмотрите на сгенерированный 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).

Ответ 4

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

В действительности основной причиной путаницы является ошибочное убеждение в том, что различные формы тестирования равенства и неравенства должны давать одинаковый результат, несмотря на то, что различные семантики полезны в разных обстоятельствах. Например, с арифметической точки зрения полезно иметь 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, если вы хотите проверьте, что что-то пустое.

Ответ 5

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

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

Ответ 6

Это определенная странность 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

Ответ 7

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