在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测试