113

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

为什么有区别?

4

7 回答 7

90

VB.NET 和 C#.NET 是不同的语言,由对使用做出不同假设的不同团队构建;在这种情况下,NULL 比较的语义。

我个人偏爱 VB.NET 语义,它本质上赋予 NULL 语义“我还不知道”。然后将 5 与“我还不知道”进行比较。自然是“我还不知道”;即NULL。这具有在(大多数如果不是全部)SQL 数据库中镜像 NULL 行为的额外优势。这也是对三值逻辑的更标准(比 C# 的)解释,如此所述。

C# 团队对 NULL 的含义做出了不同的假设,从而导致您表现出的行为差异。Eric Lippert 写了一篇关于 C# 中 NULL 含义的博客Per Eric Lippert:“我还在这里这里写了关于 VB / VBScript 和 JScript 中空值的语义”。

在任何可能出现 NULL 值的环境中,重要的是要认识到排中定律(即 A 或 ~A 在重言式上为真)不再可以依赖。

更新:

A bool(相对于 a bool?)只能取值 TRUE 和 FALSE。然而,NULL 的语言实现必须决定 NULL 如何通过表达式传播。在 VB 中,表达式5=null和两者都5<>null返回 false。在 C# 中,在可比较的表达式中5==null5!=null只有第二个第一个[updated 2014-03-02 - PG]返回 false。但是,在任何支持 null 的环境中,程序员都有责任了解该语言使用的真值表和 null 传播。

更新

Eric Lippert 关于语义的博客文章(在下面的评论中提到)现在位于:

于 2013-03-20T12:56:55.170 回答
37

因为x <> y返回Nothing而不是true. 它只是没有定义,因为x没有定义。(类似于 SQL 空值)。

注意:VB.NET Nothing<> C# null

您还必须Nullable(Of Decimal)仅在 a 有值时才比较它的值。

所以上面的 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) '
于 2013-03-20T12:55:46.293 回答
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");
    }
}

视觉基础:

[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与 Visual Basic 中的 always Nothing,not false 相比(它与 SQL 中的相同)。

于 2013-03-20T12:58:48.287 回答
6

这里观察到的问题是一个更一般问题的特例,即至少在某些情况下可能有用的不同相等定义的数量超过了表达它们的常用方法的数量。在某些情况下,这个问题会因为一种不幸的信念而变得更糟,即使用不同的相等性测试方法会产生不同的结果是令人困惑的,而这种混淆可以通过尽可能让不同形式的相等性产生相同的结果来避免。

实际上,混淆的根本原因是一种错误的信念,即认为不同形式的等式和不等式测试应该会产生相同的结果,尽管不同的语义在不同的情况下是有用的。例如,从算术的角度来看,能够使Decimal仅在尾随零的数量上不同的比较相等是有​​用的。对于double像正零和负零这样的值也是如此。另一方面,从缓存或实习的角度来看,这种语义可能是致命的。例如,假设一个人有一个Dictionary<Decimal, String>这样的myDict[someDecimal]应该等于someDecimal.ToString()。如果一个对象有很多,这样一个对象似乎是合理的Decimal想要转换为字符串并期望有很多重复的值。不幸的是,如果使用这种缓存来转换 12.3 m 和 12.40 m,然后是 12.30 m 和 12.4 m,则后面的值将产生“12.3”和“12.40”而不是“12.30”和“12.4”。

回到手头的问题,比较可空对象是否相等的明智方法不止一种。C# 认为它的==操作符应该反映Equals. VB.NET的立场是它的行为应该反映一些其他语言的行为,因为任何想要这种Equals行为的人都可以使用Equals. 从某种意义上说,正确的解决方案是使用三向“if”构造,并要求如果条件表达式返回三值结果,代码必须指定在这种null情况下应该发生什么。由于这不是语言的选择,因此下一个最佳选择是简单地了解不同语言的工作原理并认识到它们是不同的。

顺便说一句,Visual Basic 的“Is”运算符(C 中缺少)可用于测试可空对象是否实际上为空。虽然人们可能会合理地质疑if测试是否应该接受 a Boolean?,但让正常的比较运算符返回Boolean?而不是Boolean在可空类型上调用时是一个有用的特性。顺便说一句,在 VB.NET 中,如果尝试使用等式运算符而不是Is,则会收到一条警告,指出比较的结果将始终为,如果想要测试某项是否为空,则Nothing应该使用。Is

于 2013-03-20T20:47:33.277 回答
3

也许 这篇 文章可以很好地帮助你:

如果我没记错的话,VB 中的“Nothing”表示“默认值”。对于值类型,这是默认值,对于引用类型,这将是 null。因此,没有为结构分配任何内容根本没有问题。

于 2013-03-20T12:55:07.207 回答
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
于 2013-03-20T12:59:47.277 回答
0

您的 VB 代码完全不正确 - 如果将“x <> y”更改为“x = y”,结果仍然是“false”。对于可空实例,最常见的表达方式是“Not x.Equals(y)”,这将产生与 C# 中的“x!= y”相同的行为。

于 2013-03-20T15:44:11.283 回答