“x is null”和“x is null”有什么区别?和"x == null"?

在c# 7中,我们可以使用

if (x is null) return;

而不是

if (x == null) return;

使用新方法(前一个例子)比旧方法有什么优点吗?

语义有什么不同吗?

只是品味的问题吗?如果不是,什么时候我应该使用一个而不是另一个?

参考:c# 7.0有什么新功能

126777 次浏览

Roslyn编译器已更新,使两个操作符的行为相同当没有重载的相等运算符时。请参阅当前编译器结果中的代码(代码中的M1M2),它显示了没有重载相等比较器时会发生什么。它们现在都有更好的==行为。如果有一个重载的相等比较器,代码仍然不同

有关Roslyn编译器的旧版本,请参阅下面的分析。


对于null,它与我们所习惯的c# 6没有区别。然而,当你将null改为另一个常数时,事情就变得有趣了。

举个例子:

Test(1);


public void Test(object o)
{
if (o is 1) Console.WriteLine("a");
else Console.WriteLine("b");
}

测试产生a。如果你将它与你通常会写的o == (object)1进行比较,它确实有很大的不同。is会考虑比较对象另一侧的类型。太酷了!

我认为== null vs. is null常量模式只是非常熟悉的“偶然”,其中is操作符和等于操作符的语法产生相同的结果。


正如svick注释的那样,__ABC0调用__ABC1,其中__ABC2调用ceq

is的IL:

IL_0000: ldarg.1              // Load argument 1 onto the stack
IL_0001: ldnull               // Push a null reference on the stack
IL_0002: call bool [mscorlib]System.Object::Equals(object, object) // Call method indicated on the stack with arguments
IL_0007: ret                  // Return from method, possibly with a value

==的IL:

IL_0000: ldarg.1              // Load argument 1 onto the stack
IL_0001: ldnull               // Push a null reference on the stack
IL_0002: ceq                  // Push 1 (of type int32) if value1 equals value2, else push 0
IL_0004: ret                  // Return from method, possibly with a value

因为我们谈论的是null,所以与这个只对实例有影响. c没有区别。当重载相等运算符时,这可能会改变。

重载等于运算符

实际上,当你将null与一个重载了==操作符的类型进行比较时,这两种比较在语义上是不同的。foo is null将使用直接引用比较来确定结果,而foo == null当然会运行重载的==操作符(如果存在的话)。

在这个例子中,我在重载的==操作符中引入了一个“bug”,导致它总是在第二个参数为null时抛出异常:

void Main()
{
Foo foo = null;


if (foo is null) Console.WriteLine("foo is null"); // This condition is met
if (foo == null) Console.WriteLine("foo == null"); // This will throw an exception
}


public class Foo
{
public static bool operator ==(Foo foo1, Foo foo2)
{
if (object.Equals(foo2, null)) throw new Exception("oops");
return object.Equals(foo1, foo2);
}


// ...
}

foo is null的IL代码使用ceq指令执行直接引用比较:

IL_0003:  ldloc.0     // foo
IL_0004:  ldnull
IL_0005:  ceq

foo == null的IL代码使用重载操作符的调用:

IL_0016:  ldloc.0     // foo
IL_0017:  ldnull
IL_0018:  call        UserQuery+Foo.op_Equality

所以区别在于,如果你使用==,你就有运行用户代码的风险(这可能会有意想不到的行为或性能问题)。

泛型的限制

使用is null构造将类型限制为引用类型。编译器确保了这一点,这意味着你不能在值类型上使用is null。如果你有一个泛型方法,你将不能使用is null,除非泛型类型被约束为引用类型。

bool IsNull<T>(T item) => item is null;                  // Compile error: CS0403
bool IsNull<T>(T item) => item == null;                  // Works
bool IsNull<T>(T item) where T : class => item is null;  // Works

感谢大卫·奥古斯托·比利亚指出这一点。

当您尝试将非空变量与空值进行比较时,还有一个区别。当使用==时,编译器将发出警告,而当使用is时,编译器将发出错误。大多数情况下,99%的情况下,您希望编译器因为这样一个基本的错误而对您大喊大叫。+1的is null

enter image description here

enter image description here

附注:用NetCore3.1在https://dotnetfiddle.net/上测试