对象数据存内存堆里,对象一致即内存堆相同,对象相等为哈希表中键同值可能不同

 

简单的讲:相等性(quality)就是两个对象,它们的值相等。同一性(identity)就是指引用的是否为同一个对象。 下面是我的读书笔记: C# 中有两种不同的相等:引用相等和值相等。值相等是大家普遍理解的意义上的相等:它意味着两个对象包含相同的值。例如,两个值为 2 的整数具有值相等性。

引用相等意味着要比较的不是两个对象,而是两个对象引用,这两个对象引用所引用的是同一个对象。我们这里把值相等叫做对象的“相等性(equality)”,把引用相等叫做对象的同一性(identity)。

我们都知道在System.Object类型中提供了一个名为Equals的虚方法,它的作用是在两个对象相等的情况下返回true,不相等时返回false。等等,这里的说的“相等”是哪个?是“相等性(equality)”还是“同一性(identity)”。

好吧,来看一下代码吧,System的Object方法是像下面这样实现的:

public class Object { public virtual Boolean Equals(Object obj)

{

      // 如果两个引用指向的是同一个对象,那么它们肯定相等

     if (this == obj) return true;

    // 假定对象不相等

    return false;

}

}

表明上看它好像是实现的很合理:

假如this和Obj引用同一对象,自然就是true了,因为Equals知道一个对象肯定等于它自身。

然而,如果this和Obj引用不同的对象哪?,Equals就不能肯定对象是否包含相同的值,

所以总结一句话就是:Object的Equals方法实现的只是“同一性(identity)”,而不是“相等性(equality)”。

多么的令人遗憾啊~!Object的Equals的默认实现并不合理,既然不合理我们就来重写它吧,先来看看如何在内部正确实现一个Equals方法(我把它概括为四个字——空、型、值、基):

如果obj参数为null,就返回false;

如果this和obj参数引用不同的类型对象,返回false;

针对类型定义的每个实例字段,将this对象的值和obj对象的值进行对比,任何字段不相等,就返回false;

调用基类的Equals方法,以便比较它定义的任何字段。如果基类的Equals方法返回false,就返回false;否则返回true;

再来看一下Object的Equals方法的实现代码:

    public class Object

   {

        public virtual Boolean Equals(Object obj)

        {

             if (obj == null)

             return false;

            if (this.GetType() != obj.GetType())

            return false;

           // 如果对象属于相同的类型,那么在它们的多有字段都匹配的前提下返回true

          // 由于System.Object没有定义任何字段,所以字段是匹配的

          return true;

       }

}  

其实,这里要说一下,Microsoft并没有这样去实现他的代码,而是要比这个复杂的多的多。

 那么Equals方法可以在子类中重写,那么就不可以用Equals方法来测试同一性(identity)了。怎么办啊?

Microsoft在Object中提供了一个静态方法ReferenceEquals,其原型如下:

public class Object

{

  public static Boolean ReferenceEquals(Object objA, object objB)

  {

  return (objA == objB);

  }

}

注意了啊,如果想要检查同一性(identity),那么务必调用ReferenceEquals,而不应该使用C#的==操作符(除非事先把它们转化为Object类型),

原因是其中某个操作数的类型可能重载了==操作符,为其赋予了其它语义。

另外,System.ValueType重写了Object的Equals方法。并进行了正确的实现来执行相等性(equality)检查,而不是同一性(identity)检查。

在内部,ValueType的Equals方法是像这样实现的:

  如果obj参数为null,返回false; 如果this和obj引用的不同类型的对象,返回false;

  针对类型定义的每个实例字段,都将this对象的值和obj对象中的值进行比较。

  如果有字段不相等,就返回false; 返回true;

  ValueType的Equals的方法不会调用Object的Equals方法。

  顺便说一下,ValueType的Equals方法是通过反射技术来完成的。由于CLR反射机制较慢,所以在定义自己的值类型时,应该重写Equals方法,并提供自己的实现,以便提高性能。当然在自己的实现中不要调用base.Equals。

   要重写Equals,必须遵循一下几点特性: x.Equals(x) 返回 true。 (自反性) x.Equals(y) 与 y.Equals(x) 返回相同的值。 (对称性) 如果 (x.Equals(y) && y.Equals(z)) 返回 true,则 x.Equals(z) 返回 true。 (传递性) 只要不修改 x 和 y 所引用的对象,x.Equals(y) 的后续调用就返回相同的值。 (一致性) x.Equals(null) 返回 false。

 

  对象们都住在不同的房间里,每个房间只能住一个对象.对象们都被锁在房间里,永远没有办法搬家(至少从我们讨论的角度来说,这个说法是正确的).所以如果你知道了一个对象的

房间号,就能找到对应的对象. 现在假如我们有两张名片,上面如果写着相同的房间号,我们就可以断定,这两张名片是同一个对象分发出来的,这就是同一性,也就是你所说的一致. 假如1

号房里住着一个值为1的整数对象, 2号房里住着另一个值为2的整数对象,3号房里住着另另一个值为1的整数对象.我们又有它们各自的一张名片, 那么,第一个名片和第三个对应的对

象的值是相等的,但是它们不是同一个对象,用你的词来说,也就是说它们"相等",但不"一致". 如果上面说得太清楚了,那么这里让你再困扰一会吧,哈哈: 这里的名片就是引用(i, count

之类的变量/字段等的名称); 房间所在的大楼就是内存,房间号就是内存地址. 对象就是内存里保存的数据.

 

  对象们都住在不同的房间里,每个房间只能住一个对象.对象们都被锁在房间里,永远没有办法搬家(至少从我们讨论的角度来说,这个说法是正确的).所以如果你知道了一个对象的房间号,就能找到对应的对象.

现在假如我们有两张名片,上面如果写着相同的房间号,我们就可以断定,这两张名片是同一个对象分发出来的,这就是同一性,也就是你所说的一致.


假如1号房里住着一个值为1的整数对象, 2号房里住着另一个值为2的整数对象,3号房里住着另另一个值为1的整数对象.我们又有它们各自的一张名片, 那么,第一个名片和第三个对应的对象的值是相等的,但是它们不是同一个对象,用你的词来说,也就是说它们相等,但不一致.


如果上面说得太清楚了,那么这里让你再困扰一会吧,哈哈:


这里的名片就是引用(i, count之类的变量/字段等的名称);


房间所在的大楼就是内存,房间号就是内存地址.对象就是内存里保存的数据.