在之前的文章中,我们讲到了使用C#中提供的Object类的虚Equals方法来判断Equality,但实际上它还提供了另外一种判断Equality的方法,那就是使用==运算符。许多童鞋也许会想当然的认为==不过是Equals方法的语法糖而已,然而事实却并非如此。尽管从实现上来说,它给出的判定结果往往和Object.Equals方法一致,但这不是必须的,因为两者的实现机制完全相同。下面就让我们看看两者的区别。

1、==和基元类型

  让我们首先从最基础的基元类型,如int、float、double等开始,看看==是如何对他们进行相等性测试的。

class Program
{
static void Main(String[] args)
{
int number1 = 9;
int number2 = 9; Console.WriteLine(number1.Equals(number2));
Console.WriteLine(number1 == number2); Console.Read();
}
}

  上面的代码中,我们比较两个整数的相等性,其中用到了两种比较方式,第一种调用Int32类型对Object.Equals方法的重载版本,第二种调用==运算符。两种比较方法的结果都显示true,似乎两者的实现机制都是调用的Equals方法进行比较一样。下面让我们使用ildasm.exe实用程序来验证一下这个猜想是否正确吧。

  上面这条IL语句对应于源码中的第一个比较方式,可以看到它是通过调用Object.Equals实现的,该方法的定义位于System.Int32类型中,并且是实现了IEquatable<int>接口。

  下面来看==操作符对应的IL语句。   

  可以看到,==操作符生成的IL并没有调用Object.Equals,而是生成了一条ceq指令,该指令的作用是比较加载到栈上的两个值并且是通过CPU寄存器进行相等性比较的。

  总结:对于基元类型的相等性判断而言,C#中==操作符是通过ceq指令实现的,而非Object.Equals方法。

2、==和引用类型

  接下来让我们看下==如何对引用类型进行相等性判定。

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

  运行上面的代码,结果会显示两个False。这不由得让我们猜测:==运算符和Equals方法一样,也是作引用相等性判定,而不是值相等性判定。接下来让我们通过ildasm.exe查看IL代码确认一下。

  可以看到,对于C1.Equals(C2)而言,它生成的IL代码,调用的是Object.Equals方法,由于没有重载,故默认进行引用相等性测试。而对于C1==C2,它对于生成的IL代码,仅是一条ceq指令,这里用来判定引用相等性。

  也许有童鞋好奇==是如何作引用相等性判定的。这是因为引用类型的变量持有的是同类型的实例对象的内存地址,而内存地址不过是一个数字,因此可以用ceq判等性测试,就像对整数类型的判等测试一样。

  总结:对于引用类型的判等而言,==操作符是通过ceq指令比较内存地址来实现的。

3、==和String类型

  同上,还是通过一段简单的代码测试==如何对String类型作相等性测试。

class Program
{
static void Main(String[] args)
{
string str1 = "hello";
string str2 = String.Copy(str1); Console.WriteLine(ReferenceEquals(str1, str2));
Console.WriteLine(str1 == str2);
Console.WriteLine(str1.Equals(str2)); Console.Read();
}
}

  测试以上代码,结果显示:False True True。答案已经很明朗了,==操作符对String类型作值相等性测试。下面通过IL代码揭秘一下==的运行机制。

  从以上的IL代码片段中,我们并未发现ceq指令,而是调用了一个op_equality(string, string)方法。那么该方法是如何产生的呢?实际上,这是由于String类重载了==运算符导致的。在C#中,如果重载了==运算符,那么编译器就会编译生成一个static方法,名字为op_equality。

  若在VS中通过导航到定义来查看String类的源代码,会发现String类实现了两个运算符重载方法,一个是相等性测试,另一个是不等性测试。

  当我们为自己的类型重载==操作符的实现时,应该牢记一点:为了通过编译,应为==和!=同时提供重载实现。

  总结:

  1、通过上面的学习,我们知道对于引用类型,==操作符会以下面两种方式之一进行判等测试。

    • 若存在==的重载实现,那么编译器将把它编译为一个static方法。
    • 若不存在==的重载实现,编译器将它编译为一条ceq指令,比较内存地址。

    2、当我们更改一个类型的判等逻辑时,应该同时为Equals方法和==提供实现,并且应保证两者的比较结果一致,否则,使用该类型的开发人员将感到困惑。

 4、==和值类型

  通过上面的学习,我们已经晓得==如何对基元类型和引用类型作判等测试。但还未提及非基元值类型,下面让我们看看==操作符如何

  还是以上面提到的Customer类为例,只是这次我们将它变更为struct。代码如下:

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

  若运行上面的程序,会产生如下的编译错误:

  该错误清楚地向我们指明:不存在用于非基元值类型的重载==操作符。若要使用==进行判等,必须由我们提供。现在让我们在Customer结构的定义中添加以下代码:

 public static bool operator ==(Customer p1, Customer p2)
{
}

  此时,Main方法中的代码就不会产生编译错误了,但是该程序仍不能编译通过,因为operator ==方法的返回值类型为bool,而我们并没有提供任何返回值。但这并不重要,我们仅仅在这里探讨了应为非基元值类型提供==操作符的重载实现,具体的实现代码根据自己的业务需求填充就行了。

5、总结

  下面让我们来总结一下==和Equals方法对各种类型进行判等性测试的逻辑:

  • 对于基元类型,比如intfloatlongbool等,两者均比较值,故比较结果一致。
  • 对于大多数引用类型而言,==和Object.Equals方法均默认比较引用,但可以选择重载==和Equals方法,但为了不使该类型的使用者感到困惑,应同时overload或override两者,并使两者的判定结果保持一致。
  • 对于非基元值类型而言,Object.Equals方法将通过反射进行值相等性测试,但它的性能低下,实践中最好override该方法以实现快速判等;而==操作符默认情况下不可用,若想用需自己实现。

  由于==操作符比较简洁,因此在平常开发中更受喜爱,但它也存在缺陷,我将在下篇博文中进行介绍。

最新文章

  1. [skill] strncpy里边有两个坑
  2. 20161127-adt bundle
  3. Sa yo na ra
  4. 探索javascript----事件对象下的各种X和Y
  5. Penetration Testing、Security Testing、Automation Testing
  6. 算法导论:Trie字典树
  7. http://xss.heimaoseoer.com/TIqiri?1413093855
  8. (转)Building MariaDB on Mac OS X using Homebrew
  9. C#控件系列--文本类控件
  10. Android-管理Activity生命周期 -重新创建Activity
  11. SVN-Attempted to lock an already-locked dir错误
  12. Codeblocks快捷键
  13. OC类的介绍
  14. python 人工智能资源推荐
  15. idea Empty git --version output:解决
  16. k8s总结(脑图图片)
  17. angular2.0学习笔记1.开发环境搭建 (node.js和npm的安装)
  18. POJ - 1191 棋盘分割 记忆递归 搜索dp+数学
  19. SpringMyBatisDay03
  20. OpenACC 绘制曼德勃罗集

热门文章

  1. solr删除全部索引数据
  2. android自动化之MonkeyRunner测试环境配置(一)
  3. 内存表 FDMemTable ClientDataSet CreateDataSet 动态创建字段
  4. 可视化库-Matplotlib-3D图(第四天)
  5. mac下的一个类似“_kbhit()”实现
  6. 11 MySQL--Navicat与pymysql模块
  7. Git----时光穿梭机之管理修改04
  8. javascript中所谓的“坑”收录
  9. MVC-READ4
  10. java代码实现网络远程开机