代码之家  ›  专栏  ›  技术社区  ›  Andrei Rînea

编译器为什么不至少对此发出警告==空

  •  21
  • Andrei Rînea  · 技术社区  · 14 年前

    为什么C编译器甚至没有抱怨这段代码的警告?:

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

    显然情况会 从未 满意..

    5 回复  |  直到 13 年前
        1
  •  31
  •   Mark Rushakoff    14 年前

    因为你可以 override operator == 为了那个案子还真的。

    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() 将在控制台上打印“真”。

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

    重载中的常见错误 操作员== 是用来 (a == b) , (a == null) ,或者 (b == null) 检查参考相等性。这会导致调用重载 操作员== ,导致无限循环。使用 ReferenceEquals 或者将类型强制转换为对象,以避免循环。

    可能会得到@aaronaught的回复。这也是你应该做的原因 (object)x == null ReferenceEquals(x, null) 不做一个简单的 x == null ,当您检查空引用时。当然,除非你确信 == 没有重载运算符。

        2
  •  24
  •   Eric Lippert    14 年前

    真的。。。我想我错得太离谱了

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

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

    事实上,编译器 跟踪给定表达式在法律上是否可以为空,以便在非虚拟方法调用上实现较小的优化。如果你有一个非虚拟方法m,你说 foo.M(); 然后编译器生成“使用接收器foo对m进行虚拟调用”。为什么?因为我们想在foo为空时抛出,而虚拟调用总是对接收器执行空检查。非虚拟调用不会;我们必须将其生成为“检查foo是否为空,然后对m执行非虚拟调用”,这是一个更长、更慢、更令人恼火的代码。

    现在,如果我们 可以 不做空值检查就走吧。如果你说 this.M() (new Foo()).M() 然后我们不生成虚拟呼叫。我们在没有空检查的情况下生成非虚拟调用,因为我们知道它不能为空。

    所以编译器有很好的数据来说明与空值的特殊比较有时、总是或永远不会成功。

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

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

    在这种情况下:

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

    在两个可为空的int上定义了一个相等运算符。x可转换为nullable int。nullable可转换为nullable int。因此,equality运算符是有效的,因此被使用,当然始终为false。编译器给出一个警告,说明表达式始终为假。

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

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

    一言为定。可以为空的guid相等运算符有效,但不显示警告。我不确定我们是否为C 4修正了这个错误。如果没有,希望我们能把它放到一个服务包中。

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

    • 警告是编译器功能
    • 在利用编译器特性之前,必须(1)考虑(2)设计(3)实现(4)测试(5)记录和(6)发送给您。
    • 没有人做过 任何 在这六个必要的功能中,我们不能发布我们一开始从未想到的功能。
    • 因此,没有这样的特征。

    有无限多的编译器特性我们还没有想到;我们没有实现它们中的任何一个。

    这里不给出警告的另一个原因是我们试图在代码上给出警告,这两个代码都是 可能是偶然打出来的 几乎肯定是错误的,因为一个不明显的原因 . "这个==null“不太可能是偶然输入的,尽管它几乎肯定是错误的,正如您在问题陈述中所指出的,它是 明显地 错了。

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

        3
  •  4
  •   Community datashaman    7 年前

    事实上,情况真的 can be satisfied 至少在Visual Studio 2008中。他们已经在2010年的vs中修正了这种行为,但也不是完全不可能有另一种方法来创造这样的条件。

    (TL;DR版本-在C 3.5中,引用是合法的 this 来自作为构造函数参数传递的匿名函数,但是如果您真的尝试使用它,您会发现 null )

        4
  •  3
  •   Andrei Rînea    13 年前

    虽然下面的代码是一个混蛋,但仍然是C。如果您调用BAR,您将得到一个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();
        }
    }
    
        5
  •  2
  •   kemiller2002    14 年前

    这也符合其他警告,如:

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

    不管怎样,这些都会有相同的结果。