在c# 7中,我们可以使用
if (x is null) return;
而不是
if (x == null) return;
使用新方法(前一个例子)比旧方法有什么优点吗?
语义有什么不同吗?
只是品味的问题吗?如果不是,什么时候我应该使用一个而不是另一个?
参考:c# 7.0的新功能。
在c# 7中,我们可以使用
if (x is null) return;
而不是
if (x == null) return;
使用新方法(前一个例子)比旧方法有什么优点吗?
语义有什么不同吗?
只是品味的问题吗?如果不是,什么时候我应该使用一个而不是另一个?
参考:c# 7.0的新功能。
更新:Roslyn编译器已经更新,当没有重载相等操作符时,两个操作符的行为相同。请参阅当前编译器结果中的代码(代码中的M1和M2),其中显示了没有重载相等比较器时会发生什么。它们现在都有更好的执行==行为。如果有重载的相等比较器,代码仍然不同。
有关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进行比较,它确实有很大的不同。是考虑到比较的另一方的类型。太酷了!
我认为== null vs. is null常量模式是非常熟悉的“偶然”,其中is操作符和等于操作符的语法产生相同的结果。
正如svick所评论的,is null调用系统。Object::Equals(Object, Object),其中==调用ceq。
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_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,所以没有区别,因为这只对实例有影响。当重载相等运算符时,这可能会改变。
重载等于运算符
实际上,当您将null与重载了==操作符的类型进行比较时,这两种比较在语义上是不同的。Foo is null将使用直接引用比较来确定结果,而Foo == null当然会运行重载的==操作符(如果存在的话)。
在这个例子中,我在重载的==操作符中引入了一个“bug”,导致它总是在第二个参数为空时抛出异常:
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
感谢David Augusto Villa指出这一点。
当您尝试将非空变量与空值进行比较时,还有一个区别。当使用==时,编译器将发出警告,而当使用is时,编译器将发出错误。大多数情况下,99%的情况下,您希望编译器因为这样一个基本的错误而对您大喊大叫。+1 for为空。
附注:在https://dotnetfiddle.net/上用NetCore3.1测试