【问题标题】:What is the difference between "x is null" and "x == null"?“x 为 null”和“x == null”有什么区别?
【发布时间】:2017-04-02 06:23:00
【问题描述】:

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

if (x is null) return;

而不是

if (x == null) return;

与旧方法相比,使用新方法(前一个示例)有什么优势吗?

语义有什么不同吗?

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

参考:What’s New in C# 7.0

【问题讨论】:

  • 那是我刚刚查看的链接,但是它并没有给你太多信息,这就是为什么我猜 OP 在问这个问题。页面最重要的部分是这个测试 is 运算符 “is”运算符用于检查对象的运行时类型是否与给定类型兼容。换句话说,我们使用“is”运算符来验证对象的类型是否符合我们的预期。我们来看看它的语法:
  • @SimonPrice 那是关于C#的当前版本:C# 6。这个问题是关于C# 7,有pattern matching
  • @bigown 你在寻找什么样的细节?
  • @PatrickHofman 那种 svick 回答的例子

标签: c# .net null pattern-matching c#-7.0


【解决方案1】:

更新: Roslyn 编译器已更新,以使两个运算符的行为相同在没有重载的相等运算符时。请参阅code in the current compiler results(代码中的M1M2),它显示了没有重载相等比较器时会发生什么。它们现在都具有性能更好的 == 行为。如果有一个重载的相等比较器,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 常量模式只是“偶然”非常熟悉的东西,其中is 运算符和equals 运算符的语法产生相同的结果。


正如svick 评论的那样,is null calls System.Object::Equals(object, object) where == calls 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,那么自从这个only makes a difference on instances之后就没有什么区别了。当您重载相等运算符时,这可能会改变。

【讨论】:

  • @PatrickHofman It looks like is calls object.Equals(x, null), while == compiles as ceq. 但是结果应该和你说的一样。
  • 始终要注意== 是一个可重载的运算符。你可以有任何你想要的行为。例如这个weirdly implemented == 不会告诉你你的实例是否真的为空。另一方面,is null 对于真正的空引用将始终返回 true :) 此外,如果您的代码中有 ReferenceEquals,VS 2017 灯泡将建议更改为 is null,而不是 == null(正确)。
  • @PatrickHofman @svick 现在两个空检查编译成相同的东西,所以is 在用于检查空时不再有函数调用的开销。如需证明,请参阅@svick 在 cmets 中发布的链接。
  • @AndreasBjørnHassingNielsen 更新了我的答案。
  • @PatrickHofman IL 不应该反过来吗? == 调用 System.Object::Equals(object, object) 并且为 null 调用 ceq
【解决方案2】:

重载的等于运算符

实际上,当您将null 与重载== 运算符的类型进行比较时,这两个比较之间的语义有所不同。 foo is null 将使用直接引用比较来确定结果,而foo == 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);
    }

    // ...
}

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 指出这一点。

【讨论】:

  • 另外,note (x is null) 如果 x 是泛型类型,则需要类约束,而 (x == null) 和 object.ReferenceEquals(x, null) 则不需要。
  • 另外需要注意的是,空值合并运算符 (??) 和空值合并赋值运算符 (??=) 像 "is" 也会忽略重载的等于运算符 (==)。
【解决方案3】:

当您尝试将非空变量与空值进行比较时,也会有所不同。使用==时,编译器会发出Warning,而使用is时,编译器会发出Error。最有可能的是,99% 的情况下,您希望编译器会因为这样一个基本错误而对您大喊大叫。 +1 is null

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

【讨论】:

  • 这取决于您的视觉工作室设置。您可以轻松地告诉编译器将这些警告变成编译错误。
  • @BenjaminSutas 实际上,您可以配置 VS 以更改默认行为。但默认行为通常是大多数用户所期望的。
猜你喜欢
  • 1970-01-01
  • 2012-12-09
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
  • 2011-02-14
  • 1970-01-01
相关资源
最近更新 更多