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

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

提问人:Maniero 提问时间:11/18/2016 最后编辑:PangManiero 更新时间:11/16/2023 访问量:158732

问:

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

if (x is null) return;

而不是

if (x == null) return;

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

语义有什么不同吗?

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

参考:C# 7.0 中的新增功能

C 模式匹配 C#-7.0

评论


答:

447赞 Patrick Hofman 11/18/2016 #1

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

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


因为这与我们习惯的 C# 6 没有区别。但是,当您更改为另一个常量时,事情会变得有趣。nullnull

举个例子:

Test(1);

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

测试结果为 。如果你把它与你平时写的东西进行比较,它确实有很大的不同。 考虑比较另一侧的类型。很酷!ao == (object)1is

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


正如 svick 所评论的,是 null 调用 System.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 为 ==

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

评论

24赞 svick 11/19/2016
@PatrickHofman 看起来像调用对象。Equals(x, null),== 编译为 ceq但结果应该是一样的,正如你所说。
56赞 nawfal 4/19/2018
请始终注意,这是一个超载的运算符。你可以用它做任何你想要的行为。例如,这个奇怪实现的 == 不会告诉你你的实例是否真的是 null。 另一方面,对于 true null 引用,将始终返回 true:)此外,如果您的代码中有,VS 2017 灯泡将建议更改为 ,而不是(正确)。==is nullReferenceEqualsis null== null
6赞 Zbigniew Ledwoń 4/23/2019
@PatrickHofman IL不应该反过来吗?== 调用 System.Object::Equals(object, object) 并且为 null 调用 ceq
1赞 Frank Hileman 10/31/2019
因通过装箱值类型使示例代码复杂化而投了反对票。一旦对值装箱,参考比较就很少有用。
4赞 Majid Shahabfar 2/14/2021
从 C# 9.0 开始,引入了“is not”,您可以使用 if (obj is not null) 而不是 if (!(obj 为 null)) 忽略 != 的使用
147赞 Thorkil Holm-Jacobsen 6/12/2018 #2

重载等于运算符

实际上,当您与使运算符重载的类型进行比较时,这两个比较之间的语义存在差异。 将使用直接引用比较来确定结果,而当然会运行重载运算符(如果存在)。null==foo is nullfoo == null==

在此示例中,我在重载运算符中引入了一个“错误”,导致它总是抛出异常,如果第二个参数是:==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);
    }
    
    // ...
}

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

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

的 IL 代码使用对重载运算符的调用:foo == null

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

因此,区别在于,如果您使用,则可能会运行用户代码(这可能会产生意外行为或性能问题)。==

您还可以使用 SharpLab 在线工具查看 C# 编译器如何转换该方法:Main

private void Main()
{
    Foo foo = null;
    if ((object)foo == null)
    {
        Console.WriteLine("foo is null");
    }
    if (foo == null)
    {
        Console.WriteLine("foo == null");
    }
}

评论

5赞 David Augusto Villa 11/6/2019
此外,如果 x 是泛型类型,则 note (x is null) 需要类约束,而 (x == null) 和 object。ReferenceEquals(x, null) 不要。
9赞 Majid Shahabfar 2/14/2021
还应该注意的是,空合并算子 (??) 和空合并赋值运算符 (??=) 就像“is”一样,也忽略了重载等于运算符 (==)。
0赞 Taher 12/10/2022
这是非常有用的答案,它解释了为什么我的单元测试总是失败。
0赞 Jcl 8/9/2023
最后一点似乎不再有效了......如果 - 特别是 - 在约束 () 上设置值类型,它会失败,但目前似乎没问题bool IsNull<T>(T item) where T : struct => item is null; bool IsNull<T>(T item) => item is null;
42赞 Frederic 11/6/2020 #3

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

enter image description here

enter image description here

P.S. 在 NetCore3.1 的 https://dotnetfiddle.net/ 上进行了测试

评论

1赞 Benjamin Sutas 9/2/2021
这仅取决于 Visual Studio 设置。您可以轻松地告诉编译器将这些警告变成编译错误。
11赞 Frederic 9/3/2021
@BenjaminSutas 事实上,您可以配置 VS 以更改 de 默认行为。但默认行为通常是大多数用户所期望的行为。