Microsoft.Net框架程序设计学习笔记(7):重写Object.Equals方法

时间:2022-03-27 16:17:50
  System.Object类型提供了名为Equals的虚方法,目的为判断两个对象是否有相同的“值”。.Net框架类库(FCL)中许多方法在内部都调用了Equals方法(如:Array的IndexOf方法、ArrayList的Contains方法)。对于没有显式重写Equals的类型,Object(或重写了Equals方法的最近的那基类)提供的实现将被继承。以下代码展示了System.Object类型中的Equals方法实现:
  
  
  
class object
{
public virtual Boolean Equals(object obj)
{
//如果引用指向的是同一对象,肯定相等
if (this == obj) return true;

return false;
}

//......
}

  该方法采取的策略可能是最简单的:如果进行比较的两个引用指向的是同一个对象,方法将返回true;否则在任何其他情况下,方法都将返回false。如果我们定义了自己的类型,且希望比较它们中的字段是否相等,则Object类型提供的默认实现对我们来说是不够的,必须重写Equals方法。

  实现Equals方法有3种不同方式,我们逐一讨论。

  1. 为基类没有重写Object.Equals方法的引用类型实现Equals
        
        
        
    class MyRefType : BaseType
    {
    //引用类型字段
    RefType refobj;
    //值类型字段
    ValType valobj;

    public override bool Equals(object obj)
    {
    //如果obj为null,不可能相等
    if (obj == null) return false;

    //如果两个对象类型不同,不可能相等
    if (this.GetType() != obj.GetType()) return false;
     
    MyRefType other = (MyRefType)obj;


    // 比较其中的引用类型字段
    if ( ! object .Equals(refobj, other.refobj)) return false ;

    // 比较其中的值类型字段
    if ( ! valobj.Equals(other.valobj)) return false ;

    return true ;
    }

    // 重载==和!=操作符
    public static Boolean operator == (MyRefType o1, MyRefType o2)
    {
    return object .Equals(o1, o2);
    }

    public static Boolean operator != (MyRefType o1, MyRefType o2)
    {
    return ! (o1 == o2);
    }
    }
    在比较两个对象中的字段时,我们必须非常仔细。根据字段类型不同,比较方式亦不同。
    比较引用类型字段:
    比较引用类型字段时,我们应该调用object的静态Equals方法,该方法是比较两个引用类型对象的辅助方法,以下代码展示了该方法的内部实现:
        
        
        
    public static Boolean Equals(Object objA, Object objB)
    {
    //如果objA与objB指向同一个对象,返回true
    if (objA == objB) return true;

    //如果objA或objB为null,不可能相等
    if ((objA == null) || (objB == null)) return false;

    //判断objA与objB是否相等
    return objA.Equals(objB);
    }
    比较值类型字段:
    比较值类型字段,我们应该调用该字段类型的Equals方法来比较它们。不应该调用Object的静态Equals方法,因为值类型对象的值永远不可能为null,且调用object的静态Equals方法会对值类型对象执行装箱操作。
  2. 为基类重写了Object.Equals方法的引用类型实现Euqals
    class MyRefType : BaseType
    {
    //引用类型字段
    RefType refobj;
    //值类型字段
    ValType valobj;

    public override bool Equals(object obj)
    {
    //首先让基类比较其中的字段
    if (!base.Equals(obj)) return false;
          //以下代码与上一节中的实现相同

              
    //如果obj为null,不可能相等
              if (obj == null) return false;

              
    //如果两个对象类型不同,不可能相等
              if (this.GetType() != obj.GetType()) return false;
     MyRefType other = (MyRefType)obj;
          //比较其中的引用类型字段
    if (!object.Equals(refobj, other.refobj)) return false;

    //比较其中的值类型字段
    if (!valobj.Equals(other.valobj)) return false;

    return true;
    }

    //重载==和!=操作符
    public static Boolean operator ==(MyRefType o1, MyRefType o2)
    {
    return object.Equals(o1, o2);
    }

    public static Boolean operator !=(MyRefType o1, MyRefType o2)
    {
    return !(o1 == o2);
    }
    }
    这段代码和前一节中展示的代码大体一样,唯一差别是这里还要求比较基类型中定义的字段。
    注意:如果调用base.Equals会导致调用object.Equals方法,那就不应该调用它。
  3. 为值类型实现Equals方法
    所有的值类型都继承自System.ValueType。ValueType重写了Object的Equals方法实现。ValueType.Equals方法在内部首先使用反射机制来得到类型所有的实例字段,然后再比较它们是否相等。这种方法效率很低,但却是一个所有值类型都能继承的默认实现。下面的代码展示了System.ValueType.Equals方法的内部实现:
        
        
        
    class ValueType
    {
    public override bool Equals(object obj)
    {
    if (obj == null) return false;

    //得到this的类型
    Type thisType = this.GetType();

    if (thisType != obj.GetType()) return false;

    //取得该类型的所有公有和私有实例字段
    FieldInfo[] fields = thisType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

    for (int i = 0; i < fields.Length; i++)
    {
    //从两个对象中得到字段的值
    object thisValue = fields[i].GetValue(this);
    object thatValue = fields[i].GetValue(obj);
    //如果字段值不等,返回false
    if (!object.Equals(thisValue, thatValue)) return false;
    }

    return true;
    }

    //......
    }
    该方法已经提供了一个相当好的Equals实现,书中虽然介绍了自己提供强类型版本的Equals实现,但在该值类型与其他值类型之间存在隐式转换时,并不是一个好方法,下面仅列出代码供参考:
        
        
        
    struct MyValType
    {
    RefType refobj;
    ValType valobj;

    public override bool Equals(object obj)
    {
    if (!(obj is MyValType)) return false;

    return this.Equals((MyValType)obj);
    }

    //强类型版本Equals
    public Boolean Equals(MyValType obj)
    {
    if (!object.Equals(this.refobj, obj.refobj)) return false;
    if (!this.valobj.Equals(obj.valobj)) return false;

    return true;
    }

    //重载==操作符
    public static Boolean operator ==(MyValType v1, MyValType v2)
    {
    return v1.Equals(v2);
    }

    public static Boolean operator !=(MyValType v1, MyValType v2)
    {
    return !(v1 == v2);
    }
    }

  题外话:关于Object.ReferenceEquals静态方法

  Equals方法的目的是比较两个类型实例是否相等,如两个实例有相同的状态或值,则返回true。然而,有时我们需要判断两个引用是否指向了同一个对象,为实现这个功能,Object提供了一个名为ReferenceEquals的静态方法,它的实现如下:

  
  
  
class object
{
public static Boolean ReferenceEquals(Object objA, Object objB)
{
return objA == objB;
}
}
  使用示例:
  
  
  
ClassA obj1 = new ClassA();
ClassA obj2
=
obj1;

Console.WriteLine(Object.ReferenceEquals(obj1, obj2));

ClassA obj2
= new
ClassA();
Console.WriteLine(Object.ReferenceEquals(obj1, obj2));