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

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

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

显然,这种情况永远不会得到满足。

因为您可以覆盖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)发出警告ReferenceEquals(this, null) ? 从上面链接的底部:

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

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

哇…我猜我是可耻的错

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

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

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

现在,如果我们可以在不进行空检查的情况下离开,我们就可以。 如果你说this.M()(new Foo()).M()那么我们就不会生成虚拟调用。 我们生成非虚拟调用而不进行空检查,因为我们知道它不能为null。

因此编译器具有关于与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)”我不知道为什么我们不为此发出警告。 它似乎是一个警告的好候选人。 最可能的解释是遵循这种逻辑三段论:

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

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

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

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

实际上,至少在Visual Studio 2008中,条件确实可以满足 。他们已经在VS 2010中修复了这种行为,但是可能有另一种方法来创建这样的条件并不是完全不可思议的。

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

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

 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(); } } 

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

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

无论如何,这些也将始终具有相同的结果。