为什么编译器至少不警告这个== null

时间:2010-03-17 16:41:13

标签: c# compiler-construction null this

为什么C#编译器甚至没有对此代码发出警告? :

if (this == null)
{
   // ...
}

显然,条件将从不满足..

5 个答案:

答案 0 :(得分:31)

因为你可以override operator ==在这种情况下返回true。

public class Foo
{
    public void Test()
    {
        Console.WriteLine(this == null);
    }

    public static bool operator ==(Foo a, Foo b)
    {
        return true;
    }

    public static bool operator !=(Foo a, Foo b)
    {
        return true;
    }
}

运行new Foo().Test()会将“True”打印到控制台。

这里的另一个问题是:为什么编译器不会对ReferenceEquals(this, null)发出警告?从上面链接的底部:

  

operator ==重载的常见错误是使用(a == b)(a == null)(b == null)来检查引用相等性。这反而导致调用重载的operator ==,导致无限循环。使用ReferenceEquals或将类型转换为Object,以避免循环。

那个可能会被@ Aaronaught的回答所回答。这也是为什么当你检查空引用时,你应该做(object)x == nullReferenceEquals(x, null),而不是做一个简单的x == null。当然,除非您确定==运算符没有超载。

答案 1 :(得分:24)

  哇...我猜我是可耻的错误

我不同意。我觉得你还是说得好。

编译器知道比较是否会转到用户定义的比较运算符,编译器知道如果没有,那么'this'永远不会为空。

事实上,编译器 跟踪给定表达式是否合法地为空,以便在非虚方法调用上实现次要优化。如果你有一个非虚方法M并且你说foo.M();那么编译器会将其生成为“用接收器foo对M进行虚拟调用”。为什么?因为如果foo为null我们想要抛出,并且虚拟调用总是对接收器进行空检查。非虚拟呼叫不会;我们必须将其生成为“check foo for null,然后对M进行非虚拟调用”,这是更长,更慢,更刺激的代码。

现在,如果我们可以离开而不进行空检查,我们就这样做了。如果您说this.M()(new Foo()).M(),那么我们就不会生成虚拟通话。我们生成非虚拟调用而不进行空检查,因为我们知道它不能为空。

因此,编译器具有关于与null的特定比较是否有时,总是或永远不会成功的优秀数据。

接下来的问题是“如果编译器知道特定的比较永远不会成功,为什么不为它生成警告?”

答案是“有时候我们这样做,有时我们不这样做”。

我们在这种情况下这样做:

int x = 123;
if (x == null) ...

在两个可为空的int上定义了一个等于运算符。 x可转换为nullable int。 null可以转换为nullable int。因此,相等运算符是有效的,因此被使用,当然总是错误的。编译器会发出警告,表达式始终为false。

但是,由于我们在C#3中意外引入了一个错误,此代码不会产生该警告:

Guid x = whatever;
if (x == null) ...

同样的交易。可为空的guid相等运算符有效,但警告被抑制。我不确定我们是否修复了C#4的这个bug。如果没有,希望我们能将其纳入服务包。

至于“if(this == null)”我不知道为什么我们不为此发出警告。它似乎是警告的好候选人。最可能的解释是遵循这种逻辑三段论:

  • 警告是编译器功能
  • 编译器功能必须(1)考虑,(2)设计,(3)实施,(4)测试,(5)记录和(6)在您利用该功能之前发送给您。
  • 此功能没有人为这六项必要事项做过任何;我们无法提供我们从未想到的功能。
  • 因此,没有这样的功能。

我们还没有想到过无限多的编译器功能;我们没有实现它们。

此处不发出警告的另一个原因是我们尝试对代码进行警告,这些代码既可能是偶然输入而几乎肯定是错误的,因为非显而易见的原因< / em>的。 “this == null”不太可能被意外输入,虽然几乎肯定是错误的,但正如你在问题陈述中所指出的那样,显然是错误。

将它与我们的“guid == null”进行比较 - 这很可能是偶然发生的,因为开发人员可能会意外地认为Guid是一种引用类型。 Guids通常在C ++中通过引用传递,因此这是一个容易犯的错误。代码几乎肯定是错误的,但它以一种非显而易见的方式是错误的。所以这是警告的好候选人。 (这就是为什么非常不幸的是,由于我们引入了一个错误,这是C#2中的警告,而不是C#3的警告。)

答案 2 :(得分:4)

实际上,条件确实can be satisfied,至少在Visual Studio 2008中。他们已经在VS 2010中修复了这种行为,但是可能有另一种方法来创建这样的条件并不是完全不可想象的。 / p>

(tl; dr版本 - 在C#3.5中,从作为构造函数参数传递的匿名函数引用this是合法的,但如果你真的尝试使用它,你会发现{{1} }是this。)

答案 3 :(得分:3)

虽然下面的代码是个混蛋,但它仍然是C#。如果你调用Bar,你将得到一个InvalidOperationException,消息为null。

public class Foo
{
    static Action squareIt;
    static Foo() {
        var method = new DynamicMethod(
                                       "TryItForReal",
                                       typeof(void),
                                       Type.EmptyTypes,
                                       typeof(Foo).Module);
        ILGenerator il = method.GetILGenerator();
        il.Emit(OpCodes.Ldnull);
        il.Emit(OpCodes.Call, typeof(Foo).GetMethod("tryit"));
        il.Emit(OpCodes.Ret);
        squareIt = (Action)method.CreateDelegate(typeof(Action));
    }

    public void tryit()
    {
        if (this == null) {
            throw new InvalidOperationException("Was null");
        }
    }

    public void Bar() {
        squareIt();
    }
}

答案 4 :(得分:2)

这也符合C#所做的其他警告(或不是这样的警告),如:

if(true)
or 
if(1 == 1)

无论如何,这些也总是会有相同的结果。