当我们去查看object.cs源代码文件的时候,会发现object基类提供了三种判断相等性的方法。弄清楚每种方法存在的原因,也就是具体解决了什么问题,对我们理解.net判断对象相等性的逻辑很有帮助,下面让我们分别来看看吧!

1、Virtual Object.Equals()方法

  实际上.net中提供了几种比较相等性(equality)的方法,但是最基础的方法就数object类中定义的virtual Object.Equals()了。下面让我们以一个customer类来看看该方法的实际运作。

static void Main(string[] args)
{
Customer C1 = new Customer();
C1.FirstName = "Si";
C1.LastName = "Li";
Customer C2 = new Customer();
C2.FirstName = "San";
C2.LastName = "Zhang";
Console.WriteLine(C1.Equals(C2));
Console.Read();
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }

  上图代码的比较结果为False,这正是我们所期望的结果。因为两个Customer实例的字段值包含不同的字符序列。细心一点的童鞋也许会发现,我们并没有Customer类中定义Equals方法,它是怎么进行比较的呢?这里的Equals方法实际调用的就是Object基类的Virtual Object.Equals()方法。

  另外,有些童鞋在看到上面代码中使用Equals方法进行比较时,也许会想为何不直接使用==运算符进行比较,这样不是更简洁明了么?是的,使用==运算符确实使得代码更加具有可读性,但是,需要注意的是:它并不是.Net FrameWork框架的一部分。若想理解.net中是如何优雅的处理equality问题的,还是需要从equals方法开始学习。

  接下来,让我们稍微改变下代码,增加一个新的Customer实例C3,该实例和C1具有相同的字段值,看看会出现什么样的结果。

 static void Main(string[] args)
{
Customer C1 = new Customer();
C1.FirstName = "Si";
C1.LastName = "Li";
Customer C2 = new Customer();
C2.FirstName = "San";
C2.LastName = "Zhang";
Customer C3 = new Customer();
C2.FirstName = "Si";
C2.LastName = "Li";
Console.WriteLine(C1.Equals(C3));
Console.Read();
}

  上图代码中C1和C3的比较结果为false。至于原因,想必大多数童鞋都已经知道了,那就是Object基类的Equals虚方法比较引用相等性,即两个变量是否指向同一个实例对象。很明显,C1和C3指向两个不同的实例对象,因此,Equals比较的结果就是False。

  如果我们需要比较两个Customer实例的值,字段值相等就说他们是相等的,那么我们就需要自己去实现Equals方法,来覆盖Object提供的虚Equals方法。关于实现过程中需要注意的地方本文暂不讨论。

2、String的相等性判断

  FCL库中有几个引用类型,因为在开发中经常被开发人员拿来做相等性的比较,所以微软对他们提供了相等性的实现,该实现override了Object的Virtual Equals方法,可以比较值相等而不是引用相等。其中,最常用到的一个就是String类型。下面我们以一小段代码来演示String的Equals方法。

static void Main(String[] args)
{
string s1 = "Hello World";
string s2 = string.Copy(s1); Console.WriteLine(s1.Equals((object)s2));
}

  上面代码中,我们定义了两个sting类型的引用变量,s1和s2。两者具有相同的字符序列值,但却是两个不同的引用。

  另外,细心的童鞋也许会注意到,我们将Equals方法的参数强转到object类型,在实际开发中,明显不会这样做。这里之所以这样做,就是因为String类型定义了多个Equals方法,如下图所示。而在这里,我们需要确保String类型的对Object的Equals方法的override实现被调用。

  比较的结果正合我们的预期,String的override Equals方法比较两个字符串的内容是否包含相同的字符序列,若是则返回true,否则,返回false。

  微软在FCL中定义的引用类型并不多,对于这些引用类型,一般均提供了对Object的Equals方法的override实现,用来比较值。除了Sting类型之外,还有Delegate和Tuple。

3、Value Types的相等性判断

  这次,我们以customer类相似的例子举例,但将使用customer struct类型。

static void Main(string[] args)
{
Customer C1 = new Customer();
C1.FirstName = "Si";
C1.LastName = "Li";
Customer C2 = new Customer();
C2.FirstName = "San";
C2.LastName = "Zhang";
Customer C3 = new Customer();
C3.FirstName = "Si";
C3.LastName = "Li";
Console.WriteLine(C1.Equals(C2));
Console.WriteLine(C1.Equals(C3));
Console.Read();
}
public struct Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

  运行结果为:第一个Equals为False,第二个为True。我们知道,object基类的虚Equals方法比较引用而非值,但本例中struct是value type,若比较引用就毫无意义可言。仅就比较的结果来看,似乎在比较C1和C3的值。但在Customer struct类型的定义中并没有任何代码override了object的虚Equals方法。这是怎么做到的呢?

  答案就是:struct 类型均继承自System.ValueType(继承自 System.Object),System.ValueType类型override了object的虚Equals方法,该override方法的实现会遍历value type中的每一个字段,然后在每个字段上调用各自的Equals方法。若每个字段比较的结果均相等就返回true,否则,返回false。

3.1 Value Types相等性判断的开销

  使用微软为value type提供的默认相等性判断方法是有代价的。该override的Equals方法在内部是通过反射实现的。这是不可避免的,因为 System.ValueType是一个基类型,它不知道继承的子类型的信息。因此,只有在运行时通过反射发现自定义类型的字段信息,这就造成了性能损失。

3.2 Value Types相等性判断的可选方案

  为了快速的比较自定义value type,一般而言,我们需要自己override objece基类的Equals方法。实际上,微软已经为FCL中的大多数内置值类型提供了相应的实现。

4、Object的Static Equals方法

  使用object基类的虚Equals进行相等性判断存在一个问题,就是调用Equals方法的实例对象不能为null,否则,将抛出Null Reference Exception。这是因为不能在null上调用实例方法。

  Object的static Equals方法就是为了解决这个问题而出现的。当待比较的两个实例对象中有一个是null时,该静态Equals方法将返回false。

  相信不少童鞋会好奇,若两个实例对象均为null,会发生什么呢?答案就是返回true。在.net的世界中,null总是等于null。

  如果我们去查看Object类的static Equals方法的实现,就会发现其实它的代码逻辑十分简单明了,下面让我们一起看看吧。

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

  从上面代码中,可以看到static Equal方法首先判断两个参数是否指向同一个实例对象(包括两者都为null),若是,则直接返回true。接着判断两者之一是否为null,若是则返回false。最后,若控制流到达最后一条语句,则调用object的虚Equals方法。这意味着,static Equals方法除了进行null检查之外,它总是和virtual Equals方法返回相同的结果。此外,需要注意的是,若我们override了Object的virtual Equals方法,那么,static Equals方法中对virtual Equals的调用将自动调用override的Equals方法。

5、Object的ReferenceEquals方法

  ReferenceEquals方法存在的目的是为了比较两个引用变量是否指向同一个实例对象。不少童鞋对此持怀疑态度,virtual Equals和static Equals方法就是在比较引用相等性,有必要单独造一个方法来比较引用相等性么?

   不错,上面两个方法确实检测引用相等性,但它们不保证一定会检测,因为virtual Equals方法能被overridden来比较值而非引用。

   因此,对于没有override Equals方法的类型来说,ReferenceEquals方法将和Equals方法产生相同的结果。我们可以拿前面的String类型例子来说明这一点。

static void Main(String[] args)
{
string s1 = "Hello World";
string s2 = string.Copy(s1); Console.WriteLine(s1.Equals((object)s2));
Console.WriteLine(ReferenceEquals(s1,s2));
}

   从上面的结果可以看出,第一个Equals方法返回true,而ReferenceEquals方法返回false。

  我们知道,在C#中static方法不能被override,这就保证了ReferenceEquals方法的行为会始终保持一致,这是很有意义的,因为我们总是会需要一个稳定不变的方法来判断引用相等。

 

  

浅析Object基类提供的Equals方法的更多相关文章

  1. Object 类中的 equals方法

    1 相等与同一 如果两个对象具有相同的类型以及相同的属性值,则称这两个对象相等.如果两个引用对象指的是同一个对像,则称这两个变量同一.Object类中定义的equals 函数原型为:public bo ...

  2. 重写Object类中的equals方法

    Object是所有类的父亲,这个类有很多方法,我们都可以直接调用,但有些方法并不适合,例如下面的student类 public class Student { //姓名.学号.年纪 private S ...

  3. Java Object类中的equals方法

    Object类中的equals方法用于检测一个对象是否等于另外一个对象.在Object类中,这个方法将判断两个对象是否具有相同的引用.如果两个对象具有相同的引用,它们一定是相等的.从这点上看,将其作为 ...

  4. C# System.Object基类

    System.Object 基类 System.Object在.Net中是所有类型的基类,任何类型都直接或间接地继承自System.Object.没有指定基类的类型都默认继承于System.Objec ...

  5. 【Java】【常用类】Object 基类 源码学习

    源码总览: 有好些都是native本地方法,背后是C++写的 没有关于构造器的描述,默认编译器提供的无参构造 https://blog.csdn.net/dmw412724/article/detai ...

  6. java数组、java.lang.String、java.util.Arrays、java.lang.Object的toString()方法和equals()方法详解

    public class Test { public static void main(String[] args) { int[] a = {1, 2, 4, 6}; int[] b = a; in ...

  7. String类中的equals()方法:

    String类中的equals()方法: public boolean equals(Object anObject) { //如果是同一个对象 if (this == anObject) { ret ...

  8. .NET Framework中Object基类有哪些方法?

    ToString(),虚方法,任何子类可重写自定义 GetType(),非虚,返回类型名 Equals(),虚方法,默认情况下判定两个引用是否指向同一实例.(ReferenceEquals()功能相同 ...

  9. System.Object 基类

    System.Object在.Net中是所有类型的基类,任何类型都直接或间接地继承自System.Object.没有指定基类的类型都默认继承于System.Object. 基类特性 正由于所有的类型都 ...

随机推荐

  1. 记录在Python2.7 x64 bit 下 PyQt5.8的编译过程

    由于工作需要使用python下面的Qt库.PyQt现在只提供针对Python3.X系列的PyQt,所有需要自己手动编译.防止忘记,特意写下随笔记录备忘. 工 作  环境:Python版本:Python ...

  2. apache配置https和http的时候You don't have permission to access / on this server.

    You don't have permission to access / on this server. 是由于没有设置访问目录 今天配置httpd-ssl.conf的时候  发现这个问题 由于默认 ...

  3. SQLITE3 使用总结(转)

    前序: Sqlite3 的确很好用.小巧.速度快.但是因为非微软的产品,帮助文档总觉得不够.这些天再次研究它,又有一些收获,这里把我对 sqlite3 的研究列出来,以备忘记. 这里要注明,我是一个跨 ...

  4. Java的bitmap到C

    在很多情况下android程序员需用到c,bimap这个是一个java中的类,android底层有一个skbitmap类和其对应.先在我遇到了需要把java的bitmap传递到底层中进行一些操作.现在 ...

  5. MobaXterm 错行,乱码

    最近使用MobaXterm ssh,发现进入ssh显示中文没问题.但如果用VIM编辑文件时如果有中文,修改插入操作有时就会有串行乱码情况,改vim编码也不行.都没有完美解决.最后发现只需要在MobaX ...

  6. Python运维开发基础08-文件基础

    一,文件的其他打开模式 "+"表示可以同时读写某个文件: r+,可读写文件(可读:可写:可追加) w+,写读(不常用) a+,同a(不常用 "U"表示在读取时, ...

  7. LevelDB Compaction操作

    [LevelDB Compaction操作] 对于LevelDb来说,写入记录操作很简单,删除记录仅仅写入一个删除标记就算完事,但是读取记录比较复杂,需要在内存以及各个层级文件中依照新鲜程度依次查找, ...

  8. DNS开源服务器BIND最小配置详解

    一,简介 相对于存储和大数据领域,CDN是一个相对小的领域,但行行出状元,BIND就是CDN领域的蝉联N届的状元郎.BIND是一款非常常用的DNS开源服务器,全球有90%的DNS用BIND实现.值得一 ...

  9. win10系统的快捷键

    1.win10特有的快捷键:任务视图和虚拟桌面相关 (1)Win + Tab:查看任务视图 (2)Win + Ctrl + D:在任务视图中新建虚拟桌面 (3)Win + Ctrl + F4:关闭当前 ...

  10. Java 设计模式系列(十五)观察者模式(Observer)

    Java 设计模式系列(十五)观察者模式(Observer) Java 设计模式系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Java ...