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

What is the difference between "x is null" and "x == null"?

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

if (x is null) return;

而不是

if (x == null) return;

与旧方法相比,使用新方法(前面的例子)有什么优势吗?

语义有什么不同吗?

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

参考:What’s New in C# 7.0.

更新: Roslyn 编译器已更新,使两个运算符的行为相同当没有重载相等运算符时 .请参阅 code in the current compiler results (M1 and M2 in the code) that shows what happens when there is no overloaded equality comparer. They both now have the better-performing == behavior. If there is an overloaded equality comparer, the code still differs.

对于旧版本的 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 考虑了比较另一端的类型。太棒了!

我认为 == nullis null 常量模式只是非常熟悉的东西 'by accident',其中 is 运算符和等号运算符的语法产生相同的结果。


svick commented, is null calls System.Object::Equals(object, object) where == calls ceq.

IL is:

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,跟这个only makes a difference on instances没有区别。当您重载相等运算符时,这可能会改变。

重载等于运算符

当您将 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

感谢 David Augusto Villa 指出这一点。

当您尝试将 non-null 变量与空值进行比较时,也存在差异。当使用 == 时,编译器会发出警告,而当使用 is 时,编译器会发出错误。最有可能的是,99% 的情况下,您希望编译器因为这种基本错误而对您大喊大叫。 +1 is null.

P.S。使用 NetCore3.1

https://dotnetfiddle.net/ 上测试