.NET类型对象的判等(Equals)

申明::本文内容来自<<.NET框架程序设计>>
    .NET类型分为:基元类型(Primitive type),引用类型(Reference type),值类型(Value type),所有这些
类型的祖先是System.Object,System.Object提供了四个成员函数:Equals GetHashCode GetType ToString ,以确保每个继承类型都有一个最小的操作集合,但是在我们设计.NET类型的时候,这些成员的实现不能满足我们的需要,比如System.Object.Equals的默认实现就只是对两个比较的对象进行引用判等(确定指定的 Object 实例是否是相同的实例),即ReferenceEquals,所以,为我们自己的类型提供这些操作的重写是必须的,这样才能确保我们的类型如我们的期待运行,下面对重写Equals进行讨论.


1.引用类型
<1>类型(这里指我们自己设计的类型)直接派生自System.Object或者类型的所有基类均没有提供了非System.Object.Equals的实现

public class MyObject : object
{
  public int m_id;
  public string m_name;

  public MyObject(){}

  public override bool Equals(object obj)
  {
    if(obj==null)
      return false;
    if(obj.GetType()!=this.GetType())
      return false;
    MyObject other = (MyObject)obj;

    //比较值类型字段,使用值类型本身的Equals方法
    if(!m_id.Equals(other.m_id))
      return false;

    //比较引用类型字段,使用Object.Equals方法
    if(!Object.Equals(this.m_name,other.m_name))
      return false;

    return true;
  }
  
  public static bool operator==(MyObject o1,MyObject o2){
   return Object.Equals(o1,o2);
  }

  public static bool operator!=(MyObject o1,MyObject o2){
   return !Object.Equals(o1,o2);
  }
 }

看看Object.Equals(object objA,object objB)的实现,上面对引用类型字段的比较和==操作符号的重载用到了它,他的实现就是如果指向同一实体为真,任意为空为假,接下来才调用比较类型本身的判等方法,这样的效率显然更高。

public static bool Equals(object objA,object objB){
  if(objA==objB) reurn true;
  if((objA==null)||(objB==null)) return false;
  return
objA.Equals(objB);
}


<2>类型的基类提供了非System.Object.Equals的实现
public class MyObjectEx: MyObject
{
  public int m_idEx;
  public string m_nameEx;
  public MyObjectEx(){ }
   
  public override bool Equals(object obj)
  {
    if(!base.Equals(obj))
      return false;

    if(obj==null)
      return false;
    if(obj.GetType()!=this.GetType())
      return false;
    MyObjectEx other = (MyObjectEx)obj;

    if(!m_idEx.Equals(other.m_idEx))
      return false;

    if(!Object.Equals(this.m_nameEx,other.m_nameEx))
      return false;

    return true;
  }

  public static bool operator==(MyObjectEx o1,MyObjectEx o2){
   return Object.Equals(o1,o2);
  }

  public static bool operator!=(MyObjectEx o1,MyObjectEx o2){
   return !Object.Equals(o1,o2);
  }
}

这里不同的只是多了

if(!base.Equals(obj))
      return false;

很好理解,调用基类型的Equals实现,比较基类字段是否相等,如果这都不等,类型就不可能相等了.


2.值类型的Equals重写

首先要知道值类型都派生自System.ValueType,而System.ValueType又是派生自System.Object,我们先看看ValueType的Equals实现
public class ValueType
{
  public override bool Equals(object obj)
  {
    if(obj==null)
      return false;
    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);
      if(!Object.Equals(thisValue,thatValue))
        return false;
    }
    return true;
  }
}

这里用到了反射类System.Reflection,用来获取每个进行比较的类型的所有字段信息,以便对每个字段值进行比较.下面继续我们的值类型Equals方法重写
struct MyValueType : ValueType
{
  public string m_refType;
  public int m_valType;
  public override bool Equals(object obj){
    if(!(obj is MyValueType))
      return false;
    return this.Equals((ValueType)obj);
  }

  public bool Equals(MyValueType obj){
    if(!Object.Equals(this.m_refType,obj.m_refType))
      return false;
    if(!this.m_valType.Equals(obj.m_valType))
      return false;
    return true;
  }

  public static bool operator==(MyValueType v1,MyValueType v2)
  {
    return v1.Equals(v2);
  }

  public static bool operator!=(MyValueType v1,MyValueType v2){
    return !v1.Equals(v2);
  }
}
这里,对我们的值类型的Equals进行了重写,原书作者是说“尽管ValueType的Equals实现已经提供了一个很好的操作,并且适合绝大多数类型,但是我们仍然提供自己的Equals实现,原因是我们的实现效率更高,并且可以避免很多装箱操作”然而书的译者却否定了这个说法,他说不应该提供这样的重写,原因是这个重写有危险,传入的参数类型可能是一个可以隐式转化为MyValueType的类型,如:

if(myvaluetype.Equals(theothertype))

这样在调用的时候,首先theothertype进行的隐式转化为MyValueType,这就把theothertype的实际类型改了,因为对theothertype的隐式转化得到的肯定是MyValueType类型,这样调用的就是我们的public bool Equals(MyValueType obj),即使类型不同,字段值相同,也会得到true,这不是我们所期望的,所以这点还是佩服译者,考虑的深刻,能发现大师的错误。所以,对于值类型的Equals,我们还是不对其进行重写,就使用值类型基类ValueType提供的Equals,进行判等,它已经能满足我们的要求!但是如果确实对程序性能要求很严格,不妨我们对上面的做点修改:

public override bool Equals(object obj)
{
  if(!(obj is MyValueType))
    return false;
  MyValueType other = (MyValueType)obj;               
  if(!Object.Equals(this.m_refType,other.m_refType))
    return false;
  if(!this.m_valType.Equals(other.m_valType))
    return false;
  return true;
}

这样减少了使用反射,但也不能避免调用的时候对参数的装箱操作,不过也稍微提高了一些性能,也如译者所提及的,不管是引用类型还是值类型,不要提供强类型版本的Equals判等.

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页