为什么针对VB.NET和C#中的值检查null会有区别?


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
太恐怖了
Mikeb

8
我相信default(decimal?)将返回0,而不是null
Ryan Frame

7
@RyanFrame NO。由于它是null的类型,因此会返回null
SonerGönül2013年

4
噢......权利......在VB If条件语句不需要评估为布尔 ... uuuugh编辑:所以Nothing <> Anything = Nothing这会导致If以负/其他路线。
克里斯·辛克莱

13
@JMK:Null,Nothing和Empty实际上都稍有不同。如果它们都相同,那么您将不需要三个。
埃里克·利珀特

Answers:


88

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的语义”。

在任何可能使用NULL值的环境中,都必须认识到不再可以依赖排除中间定律(即A或〜A在重言式上是正确的)。

更新:

A bool(与a相对bool?)只能采用TRUE和FALSE值。但是,NULL的语言实现必须决定NULL如何通过表达式传播。在VB中,表达式5=null和两者都5<>null返回false。在C#,可比表达式5==null5!=null第二第一[更新2014年3月2日- PG]返回false。但是,在任何支持null的环境中,程序员都有责任知道该语言使用的真值表和null传播。

更新资料

埃里克·利珀特(Eric Lippert)关于语义的博客文章(在下面的评论中提到)现在位于:


4
感谢您的链接。我还在以下位置介绍了VB / VBScript和JScript中的null语义:blogs.msdn.com/b/ericlippert/archive/2003/09/30/53120.aspx 和此处:blogs.msdn.com/b/ericlippert/ archive / 2003/10/01 / 53128.aspx
Eric Lippert

27
仅供参考,以这种方式使C#与VB不兼容的决定引起了争议。当时我不在语言设计团队中,但是对该决定进行了大量的辩论。
埃里克·利珀特

2
@ BlueRaja-DannyPflughoeft在C#bool中不能有3个值,只能是2个。它bool?可以具有三个值。operator ==并且operator !=无论操作数的类型如何,都返回bool,而不是bool?。此外,一条if语句只能接受bool,而不能接受bool?
Servy

1
在C#中,表达式5=null5<>null无效。和5 == null5 != null,你确定这是第二个是收益false
Ben Voigt 2014年

1
@BenVoigt:谢谢。所有这些赞成票,您是第一个发现这种错字的人。;-)
Pieter Geerkens 2014年

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可空值类型 ...可空值类型可以包含与该类型的非空值版本相同的值以及空值。因此,对于可为空的值类型,将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”是否对C#返回true,对VB.Net返回false?开玩笑的是:-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与always相比Nothing,在Visual Basic中不是false(与SQL相同)。


为什么要通过反复试验来回答问题?应该可以从语言规范中做到这一点。
David Heffernan

3
@DavidHeffernan,因为这显示了语言上的明确区别。
nothrow 2013年

2
@Yossarian您认为在此问题上语言规范不明确。我不同意。IL是可能会更改的实现细节;规格不是。
Servy 2013年

2
@DavidHeffernan:我喜欢你的态度,鼓励你尝试。VB语言规范有时可能很难解析。卢西安(Lucian)已经对其进行了几年的改进,但是要弄清这类极端情况的确切含义仍然很困难。我建议您获取一份规范副本,进行一些研究并报告您的发现。
埃里克·利珀特

2
@Yossarian执行您所提供的IL代码的结果是不是会有所变化,但提供的服务将被编译成IL代码,你表现出C#/ VB代码可能发生变化(只要是IL是行为也符合语言规范的定义)。
2013年

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#的立场是,其==运算符应反映的行为EqualsVB.NET的观点是,其行为应与某些其他语言的Equals行为相同,因为任何想要该行为的人都可以使用Equals。从某种意义上说,正确的解决方案是具有三路“ if”构造,并要求如果条件表达式返回三值结果,则代码必须指定在这种null情况下应发生的情况。由于这不是使用语言的一种选择,因此下一个最佳选择是简单地学习不同语言的工作方式并认识到它们不相同。

顺便说一句,C中缺少Visual Basic的“ Is”运算符,可以用来测试可为空的对象是否实际上为null。尽管可能会合理地质疑一个if测试是否应该接受a Boolean?,但让正常的比较运算符返回Boolean?而不是Boolean在可空类型上调用时是一个有用的功能。顺便说一句,在VB.NET中,如果尝试使用相等运算符而不是Is,则将得到警告,表示比较结果始终为NothingIs如果要测试某项是否为空,则应使用该警告。


测试C#中的类是否为null是由完成的== null。测试可空值类型是否具有值是由完成的.hasValueIs Nothing操作员有什么用?C#确实有,is但是它测试类型兼容性。有鉴于此,我真的不确定您的最后一段想要说些什么。
ErikE

@ErikE:vb.net和C#都允许使用与的比较来检查可为空的类型的值null,尽管两种语言HasValue至少在已知类型的情况下都将其视为检查的语法糖(我不确定为泛型生成什么代码)。
超级猫

在泛型中,您可以在可为空的类型和重载解析方面遇到棘手的问题……
ErikE,2016年

3

可能是 这篇 文章很好地帮助您:

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


3
这不能回答问题。
David Heffernan

不,它没有任何澄清。问题是关于<>VB中的运算符,以及它如何在可为空的类型上运行。
David Heffernan

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
不熟悉时就是“怪异”。参见Pieter Geerkens给出的答案。
rskar 2013年

好吧,我也觉得VB不能重现VB的行为很奇怪Nullable<>.Equals()。可能有人希望它以相同的方式工作(这是C#所做的)。
马修·沃森

就像“一个人可能期望”那样,期望是关于经历的。C#的设计考虑了Java用户的期望。Java的设计考虑了C / C ++用户的期望。不管是好是坏,VB.NET的设计都考虑了VB6用户的期望。在stackoverflow.com/questions/14837209/…stackoverflow.com/questions/10176737/…上有
rskar 2013年

1
@MatthewWatson Nullable.NET的第一个版本中不存在的定义,它是在C#和VB.NET退出一段时间并确定其空传播行为之后创建的。老实说,您是否期望该语言与几年不会创建的类型保持一致?从VB.NET程序员的角度来看,它是Nullable,等于与语言不一致,而不是相反。(鉴于C#和VB都使用相同的Nullable定义,因此无法使其与两种语言保持一致。)
2013年

0

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


1
除非xnothing,否则x.Equals(y)将抛出异常。
Servy

@Servy :(在很多年后)再次偶然发现,并注意到我没有纠正你-“ x.Equals(y)” 不会为可空类型实例'x'引发异常。编译器对可空类型的处理方式有所不同。
Dave Doknjas

具体来说,可初始化为'null'的可空实例实际上不是设置为null的变量,而是未设置任何值的System.Nullable实例。
Dave Doknjas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.