对象判等

一直都认为对于对象判等自己明白了,可是真当与别人深入交流时,茫然了~~~痛定思痛,花了足足三个小时,整理了一番。要想深入理解这块知识,必须对CLR内存管理机制有一定的了解。废话不多说。

一、首先牢记两个基本概念:

(1)值相等:表示两个对象的数据成员按内存位分别相等,即两个对象类型相等,并且具有相等和相同的字段。
(2)引用相等:表示两个引用指向同一对象实例,也就是同一内存地址,因此可以由引用相等推出其值相等,反之则不然。

二、本质分析:

.NET对于对象判等总共有四个方法,虚拟的Equals()方法,静态的Equals()方法,静态的ReferenceEquals()方法,==操作符。现一一分析。

(1)Equals()虚方法:用于比较两个类型实例是否相等,也就是判断两个对象是否具有相同的“值”代码实现可以“表示”为

public virtual bool Equals(object obj)
{
     return InernalEquals(this,obj);
}
//其中InternalEquals可以表示为
if(this==obj)
{
     return true;
}
else
{
     return false;
}

可见默认情况下,Equals方法和referenceEquals方法是一样的,object的equals虚方法仅仅提供了最最简单的比较策略:如果两个引用指向同一对象,true,否则false。也就是判断是否引用相等。然而这种方法并未达到Equals比较两个对象值相等的目标,所以System.Object将这个任务交个其派生对象去重新实现,可以说Equals的比较结果取决于类的创建者是如何实现的,而非统一性约定。事实上,框架类中很多引用类型的Equals方法用于比较值相等,例如最典型的String类型对象是否相等,肯定关注起内容是否相等,判断的是值相等语义。

(2)Equals()静态方法:实现了对两个对象的相等性判别,其在System.Object类型中实现过程可以"表示"为(而不是说其在Object类中就是这样实现的)

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

所有Equals()静态方法的执行结果依次取决于三个条件
①是否为同一实例
②是否都为null
③第一个参数的Equals()实现

故通常情况下Equals静态方法的执行结果常常受到“判等对象”的影响,如下测试。

namespace 类型判等
{
class Program
{
static void Main(string[] args)
{
MyClassA classA
= new MyClassA();
MyClassB classB
= new MyClassB();
Console.WriteLine(Equals(classA,classB));
//true,实际上执行的是classA.Equals(classB);返回true
Console.WriteLine(Equals(classB, classA));//false,实际上执行的是classB.Equals(classA);返回false
Console.ReadLine();
}
}
class MyClassA
{
public override bool Equals(object obj)
{
return true;
}
}
class MyClassB
{
public override bool Equals(object obj)
{
return false;
}
}
}
执行结果为:
Image00011

由执行结果知道,静态Equals方法的执行取决于==操作符,和Equals虚方法这两个因素,因此决议静态Equals方法的执行,就要在

自定义类型中重写虚拟的Equals方法和重载==操作符。还有静态Equals方法可以解决两个值为null的对象的判等问题,而是用objA.Equals(objB)来判断两个null对象会抛出NullReferenceException异常。

(3)静态ReferenceEquals()方法,因为ReferenceEquals为静态方法,所以不能重写该方法,只能使用System.Object中的实现代码,具体为
        public static bool ReferenceEquals(object objA, object objB)
        {
            return (objA == objB);
        }

如下示例:

namespace 类型判等
{
class Program
{
static void Main(string[] args)
{
MyClass classA
= new MyClass();
MyClass classB
= new MyClass();
//classA,classC指向同一对象实例
MyClass classC = classA;

Console.WriteLine(ReferenceEquals(classA,classB));
//false
Console.WriteLine(ReferenceEquals(classA, classC));//true
Console.WriteLine(ReferenceEquals(null, null));//true
Console.WriteLine(ReferenceEquals(classA, null));//false
Console.ReadLine();
}
}
class MyClass
{
}
}

结果:

Image00012
可见ReferenceEquals方法用于判断两个引用是否指向同一对象,也就强调的引用相等,因此ReferenceEquals比较同一类型的两个对象实例将范虎false而.NET认为null等于null。

(4)“==”操作符

值类型下:表示是否值相等,由值类型的根类System.ValueType提供了实现,
引用类型下:表示是否“引用相等”即两个引用指向同一个对象实例。牢记此话。

当然也有例外 还是string ==表示的是值相等,而非引用相等。

三、实际应用

值类型判等

①Equals,System.Valuetype重载了System.Object的Equals方法,用于实现对实例数据的判等。

②ReferenceEquals 永远返回false说明:用ReferenceEquals()方法比较两个值类型变量毫无意义,结果肯定是false,即使是2个值相等的变量,因为在ReferenceEquals()方法比较前,被比较的值类型变量将会被装箱操作,隐性地创建两个不同对象,所以其地址引用也将不同。

③== 为重载的==的值类型,将比较两个值是否“按位”相等(不懂,只知道是比较两个值是否相等,还请高人指点!)

引用类型判等

①静态的ReferenceEquals:两个实例对象是否指向同一引用地址。

②静态的Equals:
     是否为同一实例
     是否都为null
     第一个参数的Equals()实现

③虚拟的Equals默认为引用地址比较。

④== 默认为引用地址比较

四、小结——重写Equals()方法 1、经过对四种不同类型判等方法的讨论,我发现不管是Equals静态方法,Equals虚拟方法,==操作符的执行结果,都可能受到重写Equals方法的影响,所以在对象判等时就必须注意自定义类型中如何实现Equals方法,以及实现怎么样的Equals方法,不同的类型,“相等”会有偏差。

2、因此Equals方法的执行结果往往取决于自定义类型的具体实现规则,而为什么.NET提供这种机制

(1)对象判等取决于需求,没必要为所有.NET类型完成逻辑判等,System.Object也无法满足各种需求的判等方法。

(2)不同类型的判等的处理不同,通过多态机制在派生类中处理各自的判等实现是明智的。

3、重写Equals要综合考虑值类型,引用类型的判等,同时要兼顾父类所带来的影响,另外遵循三个原则,自发,传递,对称,还要注意重写GetHashCode()方法。

另外关于对象判等还得注意String类型的字符串驻留机制导致的String判等的特殊性。

题外话:本贴主要参考园子里王涛老师写的那本《你必须知道的.NET》一书,推荐大家都去看看此书,绝对值得一看。另有不当之处,还望各位指正。

posted @ 2011-06-02 21:57  liujb  阅读(259)  评论(0编辑  收藏  举报