C#相等性 - “==”
今天写一下C#里的“==”这个操作符。
原始类型
假象
在刚学C#的时候,我以为C#里的==和.NET里的object.Equals()方法是一样的,就是一个语法糖而已。其实它们的底层机制是不一样的,只不过它们给出的结果在大多数情况下恰好相同。
看个例子:
这俩方法给出的结果都是True。
看起来这两种方式做了同样的动作,就是比较两个值。
底层原理
Build项目,然后使用ildasm看一下生成的il语言(ildasm位置大致在:C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.2 Tools)。
使用ildasm打开生成的dll,首先查看Program类里面的ByEqualMethod方法:
可以看到C#源码里调用Equals()的地方直接被翻译成il语言里相应的Equals()方法了。。。。
然后看一下ByEqualOperator这个方法:
在C#里该方法使用了==操作符,而在il语言里,我们只看到了一个叫做ceq的指令。ceq的意思是compare for equality,就是比较两个值是否相等,在运行时,它将会被转换为硬件上的比较,也许用的是CPU的寄存器。
针对原始类型,C#的==操作符并没有使用.NET里提供的那些Equals方法,这时==操作符使用专用的汇编语言指令来进行判断相等性的。
使用 == 判断引用类型的相等性
这里的引用类型不包含string。
看例子,这里我使用==来比较自定义类MyClass的两个实例是否相等:
而结果是两个False:
使用ildasm看一下ByEqualMethod()这个方法:
可以看到,a.Equals(b)调用的是virtual的object.Equals()方法,参数类型是object,这个应该都能理解。
再看一下ByEqualOperator()方法:
== 操作符翻译过来还是使用ceq对两个参数进行的比较,和之前int类型的例子一样,除了参数类型不同。
所以这应该也是使用CPU的硬件来进行判断相等性的,那么像这种引用类型是怎么通过CPU硬件来比较的呢?因为这两个类型是引用类型,所以c1,c2两个变量里面保存的是它们对应的实例在托管堆中的内存地址,也就是两个数字而已,所以当然可以进行比较了。
string
我们都知道,==用来判断string相等性的时候,比较的是string值,而不是引用地址。
看例子:
结果是两个True:
首先,使用string.Copy()方法可以保证str1和str2是两个不同的引用。
使用ildasm,先看ByEqualMethod():
可以看到,这里a.Equals(b)实际调用的是string实现的IEquatable<T>接口的Equals方法,它的参数是string。
再看一下ByEqualOperator():
这次没有使用ceq指令,而是调用了一个叫做op_Equality()的方法,这是个什么方法?
其实它是C#里 == 操作符的一个重载:static bool op_Equality(string, string)。
在C#里,当你定义一个类型的时候,你可以对==操作符进行重载,格式大概如下:
因为il语言里没有操作符的概念,而只有方法才能作为操作符的重载而存在于il里,所以这里使用的是静态方法,它会被翻译为一个特殊的静态方法叫做op_Equality()。
我们也可以直接看一下string类的源码,里面也是这样对==进行重载的:
当然,重载了==,也需要重载 !=。
小结
总结一下,使用==来判断引用类型的相等性,需要按下面的思路顺序进行考虑:
1. 该类型是否对 == 进行了重载?如果是,那就是用该重载方法;否则看2
2. 使用ceq指令来比较引用指向的内存地址。
另外还需要再提醒一下的是,string类的==和Equals()方法永远都会给出一样的结果。
还有一个原则就是,当你改变某个类型的相等性判断方法是,要确保==和Equals()方法做的是同样的事情。
值类型
非原始类型
看例子,这里有两个值类型:
当我使用==对它们进行比较的时候,直接报错了。
因为默认情况下,不可以使用==来对非原始类型的值类型进行相等性判断。要想使用==,就必须提供重载方法。
Tuple
直接看例子:
针对这两个tuple,我做了三个相等性判断,通过第一个ReferenceEquals方法我们可以知道这两个tuple变量指向不同的实例。
而tp1.Equals(tp2)返回的是True,这是因为Tuple类(引用类型)重写了object.Equals()方法,从而比较的是Tuple里面的值。
尽管微软为Tuple把object.Equals()方法重写了,但是它并没有处理==操作符,所以==还是在比较引用的相等性,所以会返回False。
这样做确实挺让人迷惑的。。。
比较==和object.Equals()方法
通常情况下,尽量使用==操作符,但是有时候==不行,需要使用object.Equals()方法,例如涉及到继承或者泛型的时候。
继承
直接看例子:
这两个字符串我做了4个相等性判断,其结果为:
无论是object的virtual Equals()方法,还是==操作符,还是object的static Equals()方法,都会返回True。
但是我做一下小小的改动:
我们看看结果会不会变:
结果发生了变化,str1==str2这次返回了False。
这是因为==操作符不是virtual的,它相当于是static的,而static的是无法virtual的。
现在 str1 == str2 这句话,我们比较的是两个类型为object的变量,尽管我们知道它们都是string,但是编译器并不知道。而针对于非virtual的方法或操作符,到底调用哪个方法是在编译时决定的,因为这两个变量的类型是object,所以编译器会选择用来比较object的代码,而object又没有==操作符的重载,所以==做的就是比较引用的相等性,而这两个string是不同的实例,所以结果会返回False。
所以(object)x == (object)y和ReferenceEquals(x, y)的结果总是一样的。
针对涉及继承的相等性判断,最好还是使用object.Equals()方法,而不是==操作符。
泛型
另一种不适合使用==操作符的情景是涉及泛型的时候,直接看例子:
这个泛型方法直接报错了,因为==操作符无法应用于这两个操作数T,T可以是任何类型,例如T是非原始类型的struct,那么==就不可用。我们无法为泛型指定约束让其实现某个操作符。针对这个例子,我可以这样做,来保证可以编译:
现在T是引用类型了,代码可以编译了。我们使用以下该方法:
按理说这就相当于调用了Equals()方法,结果应该返回True。而实际结果是:
之所以返回了False,是因为泛型方法里的==操作符比较的是引用,而这又是因为尽管编译器知道可以把==操作符应用于类型T,但是它仍然不知道具体是哪个类型T会重载该操作符,所以它会假设T不会重载==操作符,从而对待这两个操作数如同object类型一样并编译,所以判断的是引用相等性。
所以泛型方法不会选择任何的操作符重载,它对待泛型类就像对待object类型一样。
综上,针对泛型方法,应该使用Equals()方法,而不是==操作符。
C#相等性 - “==”的更多相关文章
- 代码的坏味道(17)——夸夸其谈未来性(Speculative Generality)
坏味道--夸夸其谈未来性(Speculative Generality) 特征 存在未被使用的类.函数.字段或参数. 问题原因 有时,代码仅仅为了支持未来的特性而产生,然而却一直未实现.结果,代码变得 ...
- 2000条你应知的WPF小姿势 基础篇<78-81 Dialog/Location/WPF设备无关性>
在正文开始之前需要介绍一个人:Sean Sexton. 来自明尼苏达双城的软件工程师.最为出色的是他维护了两个博客:2,000ThingsYou Should Know About C# 和 2,00 ...
- 浅谈 linux 例行性工作 crontab (linux定时任务)
定时任务大家都挺说过,就好比你手机上的闹钟,到了指定的时候就会响起. 今天在对redis缓存进行定时储存时又操作了一把,发现一些细节,写的不好.大家就将就看吧, 首先 简单介绍一下linux 例行性工 ...
- 分享一实战性开源MVC框架<Linux、Windows跨平台开发so easy>
一.引子 开源地址 https://github.com/564064202/Moon.Mvc 欢迎加入开发 .NET Core微软还在发力,但作为商用还有一段距离,很多开发库尚不能用于.NET ...
- 关系数据库SQL之可编程性触发器
前言 前面关系数据库SQL之可编程性函数(用户自定义函数)一文提到关系型数据库提供了可编程性的函数.存储过程.事务.触发器及游标,前文已介绍了函数.存储过程.事务,本文来介绍一下触发器的使用.(还是以 ...
- bzoj2693--莫比乌斯反演+积性函数线性筛
推导: 设d=gcd(i,j) 利用莫比乌斯函数的性质 令sum(x,y)=(x*(x+1)/2)*(y*(y+1)/2) 令T=d*t 设f(T)= T可以分块.又由于μ是积性函数,积性函数的约束和 ...
- hdu1452 Happy 2004(规律+因子和+积性函数)
Happy 2004 题意:s为2004^x的因子和,求s%29. (题于文末) 知识点: 素因子分解:n = p1 ^ e1 * p2 ^ e2 *..........*pn ^ en 因子 ...
- 错误:违反并发性: DeleteCommand 影响了预期 1 条记录中的 0 条
在access的mdb数据库动态更新的过程中,遇到了DeleteCommand出现DBConcurrencyException异常,错误:违反并发性: DeleteCommand 影响了预期 1 条记 ...
- bzoj1415[NOI2005]聪聪和可可-期望的线性性
这道题之前我写过一个巨逗比的写法(传送门:http://www.cnblogs.com/liu-runda/p/6220381.html) 当时的原因是这道题可以抽象出和"绿豆蛙的归宿&qu ...
- 本地数据Store。Cookie,Session,Cache的理解。Timer类主要用于定时性、周期性任务 的触发。刷新Store,Panel
本地数据Store var monthStore = Ext.create('Ext.data.Store', { storeId : 'monthStore', autoLoad : false, ...
随机推荐
- 设置firefox每次访问网页时检查所存网页的较新版本
我们做技术,经常在写页面的时候需要多次刷新测试,可是浏览器都有自己的缓存机制,一般CSS和图片都会被缓存在本地,这样我们修改 的CSS就看不到效果了,每次都去清空缓存,再刷新看效果,这样操作太麻烦了. ...
- erlang的脚本执行---escript
1.概述: 作为程序员对于脚本语言应该很熟悉了,脚本语言的优点很多,如快速开发.容易编写.实时开发和执行, 我们常用的脚本有Javascript.shell.python等,我们的erlang语言也有 ...
- webService(一)开篇
Webservice技术在web开发中算是一个比较常见技术.这个对于大多数的web开发者,别管是Java程序员还是.NET程序员应该都不是很陌生.今天我就和大家一起来学习一下webservice的基本 ...
- Java开源生鲜电商平台-用户表的设计(源码可下载)
Java开源生鲜电商平台-用户表的设计(源码可下载) 说明:由于该系统属于B2B平台,不设计到B2C的架构. 角色分析:买家与卖家. 由于买家与卖家所填写的资料都不一样,需要建立两站表进行维护,比如: ...
- 解决iframe在移动端(主要iPhone)上的问题
前言 才发现已经有一段时间没有写博客了,就简单的说了最近干了啥吧.前段时间忙了杂七杂八的事情,首先弄了个个人的小程序,对的,老早就写了篇从零入手微信小程序开发,然后到前段时间才弄了个简单的个人小程序, ...
- 程序员快递请查收,来自Python黑客大佬的一份DDOS攻击说明书!
DDoS攻击没有我们想象中的那么简单,并不是什么Python程序员都能够做到的. 若要知晓黑客利用DDOS攻击原理那么我们必须要知道是实行DDoS攻击比较难的原因是什么? 很简单的一句话概括:&quo ...
- FineReport启动后访问404
近期将FineReport以嵌入式方式部署在Tomcat8上,启动服务后,点击导出下载出现HTTP ERROR 404情况: 百思不得其解啊,纠结了好几天: 后查看原部署Tomcat6服务器的cata ...
- JDK1.8的新特性
JAVA8新特性 接口改善 现在接口里已经完全可以定义静态方法了. 举一个比较普遍的例子就是在java类库中, 对于一些接口如Foo, 都会有一个有静态方法的工具类Foos 来生成或者配合Foo对象实 ...
- Spring 的IOC和AOP总结
Spring 的IOC和AOP IOC 1.IOC 许多应用都是通过彼此间的相互合作来实现业务逻辑的,如类A要调用类B的方法,以前我们都是在类A中,通过自身new一个类B,然后在调用类B的方法,现在我 ...
- Http Header信息
REMOTE_ADDR – 访问客户端的 IP 地址 HTTP_VIA – 如果有该条信息, 就证明您使用了代理服务器,代理服务器的地址就是后面的数值. HTTP_X_FORWARDED_FOR – ...