在之前的文章中,我们讲到了使用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该方法以实现快速判等;而==操作符默认情况下不可用,若想用需自己实现。

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

浅析c#中==操作符和equals方法的更多相关文章

  1. Java中的“==操作符”和equals方法有什么区别

    Java中的"=="和equals方法究竟有什么区别? 1.==操作符 "=="操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的 ...

  2. Java中的==和equals( )方法

    在Java中,equals和==都是用于检测两个字符串是否相等,返回类型也都是boolean值,但是二者内部处理却不一样. ==与equals( ) ==在Java中是一个二元操作符,用于比较原生类型 ...

  3. 浅析C#中的“==”和Equals

    1.“==”和Equals两个真的有关联吗? 对于“==”和Equals大多数网友都是这样总结的: “==” 是比较两个变量的值相等. Equals是比较两个变量是否指向同一个对象. 如:这篇文章,并 ...

  4. Java中==号与equals()方法的区别

    String str1 = new String("abc"); String str2 = new String("abc"); System.out.pri ...

  5. 浅析java中clone()方法

    本文转载自:http://blog.csdn.net/mengxiangyue/article/details/6818611 Java中我们可能都遇到过这样的情况,在我们将一个对象做为参数传给一个函 ...

  6. java中的equals()方法重写

    如何java中默认的equals方法跟实际不符的话,需要重写equals方法.例如: public class TestEquals { public static void main(String[ ...

  7. C# 中==与Equals方法比较

    先来段代码,如下: static void Main(string[] args) { string a = new string(new char[] { 'h', 'e', 'l', 'l', ' ...

  8. Java中equals方法简略描述

    所有类都从Object中继承了equals方法,源码:public boolean equals(Object o){return this == o;} 直接判断this与o本身是否为同一对象(是否 ...

  9. Java中Set的contains()方法——hashCode与equals方法的约定及重写原则

    转自:http://blog.csdn.net/renfufei/article/details/14163329 翻译人员: 铁锚 翻译时间: 2013年11月5日 原文链接: Java hashC ...

随机推荐

  1. solr删除全部索引数据

    SOLR 删除全部索引数据: <delete><query>*:*</query></delete><commit/>

  2. Introduction to Spring Data MongoDB

    Introduction to Spring Data MongoDB I just announced the new Spring 5 modules in REST With Spring: & ...

  3. ASP.NET中的几种弹出框提示基本实现方法

    我们在.NET程序的开发过程中,常常需要和用户进行信息交互,比如执行某项操作是否成功,“确定”还是“取消”,以及选择“确定”或“取消”后是否需要跳转到某个页面等,下面是本人对常用对话框使用的小结,希望 ...

  4. sublime text3最新版本注册码(build 3143)

    —– BEGIN LICENSE —– TwitterInc 200 User License EA7E-890007 1D77F72E 390CDD93 4DCBA022 FAF60790 61AA ...

  5. libcurl使用心得-包括下载文件不存在处理相关(转)

    libcurl使用心得 Libcurl为一个免费开源的,客户端url传输库,支持FTP,FTPS,TFTP,HTTP,HTTPS,GOPHER,TELNET,DICT,FILE和LDAP,跨平台,支持 ...

  6. docker-compose搭建单机多节点es + kibana

    docker-compose.yml配置如下: version: '2.2' services: elasticsearch: image: docker.elastic.co/elasticsear ...

  7. FD_CLOEXEC

    [FD_CLOEXEC] 通过fcntl设置FD_CLOEXEC标志有什么用? close on exec, 意为如果对描述符设置了FD_CLOEXEC,使用execl执行的程序里,此描述符被关闭,不 ...

  8. 77. Combinations (Recursion)

    Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. For exampl ...

  9. C++ 基类指针,子类指针,多态

    基类指针和子类指针之间相互赋值(1)将子类指针赋值给基类指针时,不需要进行强制类型转换,C++编译器将自动进行类型转换.因为子类对象也是一个基类对象. (2)将基类指针赋值给子类指针时,需要进行强制类 ...

  10. memcached的常用命令

    memcached 常用命令及使用说明   1.启动Memcache 常用参数 -p <num> 设置TCP端口号(默认设置为: 11211) -U <num> UDP监听端口 ...