'this'关键字是否可能等于null?

Jos*_* D. 30 .net c# oop equals

在一个例子中,我的教授已经实现了Equals如下:

public class Person {
    private string dni;

    // ...

    public override bool Equals(object o) {
        if (o == null)
            return (this == null);
        else {
            return ((o is Person) && (this.dni == (o as Person).dni));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我没有使用C#的经验,但据我所知,this在成员函数中不能为null(至少在C++和Java中,我知道的语言是这样)所以if看起来很奇怪.

我是对的还是c#中有任何组件我不知道哪个使测试成为this == null必要的?

Eri*_*ert 58

我没有使用C#的经验,但据我所知,这在成员函数中不能为null(至少在C++和Java中是这样,我知道的语言)

让我们首先注意你的陈述是错误的.

在C++中,在null接收器上调度方法是未定义的行为,未定义的行为意味着任何事情都可能发生."任何东西"包括程序传递NULLthis和持续的,好像没有什么是错的.当然,this在C++中检查是否为null 是有点愚蠢的,因为如果你已经不知道你的程序在做什么,那么检查只能是真的,因为它的行为是未定义的.

thisJava中是否可以为null我不知道.

现在来解决你关于C#的问题.我们假设==没有超载.我们稍后会回到这一点.

您的方法是用C#编写的.假设它是从带有空接收器的C#程序调用的.C#编译器评估接收者是否可能为null; 如果它可能为null,那么它确保它生成在调用方法之前执行空检查的代码.因此,在这种情况下,这种检查毫无意义.这当然是99.9999%可能的情况.

假设它是通过Reflection调用的,就像在mike z的答案中那样.在这种情况下,执行调用的不是C#语言; 相反,有人故意滥用反思.

假设它是从另一种语言调用的.我们有一个虚拟方法; 如果使用虚拟调度从其他语言调用它,则必须执行空检查,因为我们怎么知道虚拟插槽中的内容?在那种情况下,它不能为空.

但是假设它是使用非虚拟调度从另一种语言调用的.在这种情况下,另一种语言不需要实现检查null的C#功能.它可以只调用它并传递null.

因此,有几种方法this可以null在C#中使用,但它们都远离主流.因此,人们很少像您的教授那样编写代码.C#程序员习惯性地认为它this不是null,从不检查它.

现在我们已经解决了这个问题,让我们更多地批评这些代码.

public override bool Equals(object o) {
    if (o == null)
        return (this == null);
    else {
        return ((o is Person) && (this.dni == (o as Person).dni));
    }
}
Run Code Online (Sandbox Code Playgroud)

首先,有一个明显的错误.我们假设this可能是null,好吧,让我们运行它. 什么停止this.dni抛出null引用异常??? 如果你假设this可以为null,那么至少要这样做!(在Coverity,我们将这种情况称为"前向无效缺陷".)

下一步:我们重写Equals然后==在内部使用,大概是指参考平等.这种方式就是疯狂!现在我们的情况x.Equals(y)可能是真的,但x==y可能是假的!这太可怕了.请不要去那里.如果你要覆盖,Equals那么同时重载==,并IEquatable<T>在你执行时实现.

(现在,有一个合理的论据可以说,疯狂在于任何一个方向;如果与价值语义==一致,Equals那么personx == persony可能会有所不同(object)personx == (object)persony,这似乎也很奇怪.这里要说的是,在C#中,相等性是相当混乱的.)

而且:如果==以后被覆盖怎么办?现在Equals正在调用一个重写的==运算符,当代码的作者明确希望进行参考比较时.这是bug的秘诀.

我的建议是(1)编写一个做正确事情的静态方法,以及(2)ReferenceEquals每次都可能对于什么样的平等意味着混淆时使用:

private static bool Equals(Person x, Person y)
{
    if (ReferenceEquals(x, y))
        return true;
    else if (ReferenceEquals(x, null))
        return false;
    else if (ReferenceEquals(y, null))
        return false;
    else 
        return x.dni == y.dni;
}
Run Code Online (Sandbox Code Playgroud)

这很好地涵盖了每一个案例.请注意,当引用相等语义时,读者可以清楚地看到它.另请注意,为了进行调试,此代码可以很容易地为每种可能性设置断点.最后,请注意我们尽早选择最便宜的; 如果对象的引用相等,那么我们就不必对字段进行潜在的昂贵比较!

现在其他方法很简单:

public static bool operator ==(Person x, Person y) 
{
  return Equals(x, y);
}
public static bool operator !=(Person x, Person y) 
{
  return !Equals(x, y);
}
public override bool Equals(object y)
{
  return Equals(this, y as Person);
}
public bool Equals(Person y)
{
  return Equals(this, y);
}
Run Code Online (Sandbox Code Playgroud)

请注意我的方式比你教授的方式更优雅和清晰.并注意我的方式处理null this而不this直接比较null.

还是那句话:这一切说明,妥协的位置到达,其中价值和参考平等是可能的,有四个(==,!=,object.Equals(object)IEquatable<T>.Equals(T))的方式来实现平等,是非常复杂和混乱,即使没有假设this能或不能null.

如果这个主题让你感兴趣,我在本周的博客中描述了一个稍微困难的问题:如何实现一般的比较,包括不平等.

http://ericlippert.com/2013/10/07/math-from-scratch-part-six-comparisons/

作为对C#如何处理平等的批评,这些评论特别有趣.

最后:别忘了覆盖GetHashCode. 确保你做得对.

  • "并注意我的方式处理一个null这对于覆盖是不正确的,因为如果"this"为null,它将等于一个非null的非null对象(因为"as"运算符)会返回null). (4认同)

Mik*_*ray 13

是的,在某些情况下.最常见的情况是,如果使用由反射创建的委托调用实例方法并传递空接收器.这将打印"True":

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

public class Program
{
    public static void Main(string[] args)
    {
        object t = new Test();
        var methodInfo = t.GetType().GetMethod("Method");
        var a = (Action)Delegate.CreateDelegate(typeof(Action), null, methodInfo);
        a();
    }
}
Run Code Online (Sandbox Code Playgroud)

老实说,虽然我从未见过有人this在生产代码中检查过null.