[C#] 类型学习笔记二:详解对象之间的比较
继上一篇对象类型后,这里我们一起探讨相等的判定。
相等判断有关的4个方法
CLR中,和相等有关系的方法有这么4种:
(1) 最常见的 == 运算符
(2) Object的静态方法ReferenceEquals
(3) Object的静态方法Equals
(4) Object.Equals()方法,这是一个virtual method
"==" 运算符
首先要知道"==" 是一个运算符,它只有在两边都为相同类型时才能通过编译。
假设“==” 没有被我们显示地重载过,当它的两边都是引用类型时,"=="在左右两边引用同一个对象时返回true,它的作用和(1)中的System.Object.ReferenceEquals相同;
当"=="两边都是没被装箱的值类型时,只有值类型重载了"=="才能通过编译,也就是说,如果我们通过struct定义了新的值类型,然后通过"=="来比较,那只有我们在struct中显示重载了"=="才能通过编译。
FCL中Int32等这些自带的值类型,虽然查看代码时没有看到其对"=="的重载,但是我相信应该有隐示地对其进行重载,重载的内容应该和Int32中的Equals()函数一致。
Object的静态方法ReferenceEquals
它的定义如下
public static bool ReferenceEquals(object objA, object objB)
{
return objA == objB;
}
显然,这个方法的作用是为了判定两个object是否指向同一个堆对象。这里有个小实例
int n = ; Object.ReferenceEquals( n, n ); object o1 = (object)n;
object o2 = (object)n;
if(o1 == o2) ...
两次的判定结果结果应该都为false,这里需要我们前一节装箱的知识。在每一次比较中,n在代码中都会两次被装箱,既然被装箱了两次,自然就是不同的对象,所以返回为false。
Object的静态方法Equal
public static bool Equals(object objA, object objB)
{
return objA == objB || (objA != null && objB != null && objA.Equals(objB));
}
从其源码可以看出,它其实就是==运算符和 (4)中所提到的object.Equals 方法的结合版。就是先判断对象的identical,再比较内容。虽然很全面,但是编程中不常用到,因为程序员在写代码时,对于到底是判断identical还是比较内容,都有明确的选择性。
Sytem.Object的提供的Equals 方法
这里才是我们的重头戏,这个方法将真正被用来比较对象的内容。
FCL中的万物之源System.Object提供的虚函数Equals的定义如下:
public virtual bool Equals(object obj){
if(this == obj) return true;
return false;
}
你没有看错,就这样没有了。
是不是有点意外?
这就是Microsoft的思路,这个virtual 方法只是意思意思罢了,真正的基于内容的比较,定义在具体的类中。
Equals 方法
System.Object所提供的virtual 方法只有被比较的对象引用同一个托管堆对象,才会返回true。而且,参数是object,也就是说如果我们自定义的类型如果没有显示override Equals的话,需要比较的时候类会被装箱。
那么,既然Object的equals如此的“弱”,那么系统本身的那些int,string等这些常见的值类型的比较的时候,内部是什么情况?
System.ValueType的Equals方法
有了第一篇笔记的铺垫,我们知道int等都是值类型,值类型是继承自System.ValueType的,System.ValueType又是继承自System.Object的。在System.ValueType中,不出意外,重写了Equals方法的实现。使用ILSpy打开System.ValueType中关于Equals的实现:
// System.ValueType
/// <summary>Indicates whether this instance and a specified object are equal.</summary>
/// <returns>true if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, false.</returns>
/// <param name="obj">Another object to compare to. </param>
/// <filterpriority></filterpriority>
[__DynamicallyInvokable, SecuritySafeCritical]
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
RuntimeType runtimeType = (RuntimeType)base.GetType();
RuntimeType left = (RuntimeType)obj.GetType();
if (left != runtimeType)
{
return false;
}
if (ValueType.CanCompareBits(this))
{
return ValueType.FastEqualsCheck(this, obj);
}
FieldInfo[] fields = runtimeType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
for (int i = ; i < fields.Length; i++)
{
object obj2 = ((RtFieldInfo)fields[i]).UnsafeGetValue(this);
object obj3 = ((RtFieldInfo)fields[i]).UnsafeGetValue(obj);
if (obj2 == null)
{
if (obj3 != null)
{
return false;
}
}
else
{
if (!obj2.Equals(obj3))
{
return false;
}
}
}
return true;
}
整个实现可以分为下面几个部分:
(1) 如果参数是null,不需说,返回false
(2) 随后通过反射获取当前对象和参数对象的类型,比较两个类型是否一致。类型如果都不一致就直接返回false了。
这里大家可能会有疑问:这里明明使用base.GetType(),怎么可以解释为“获取当前对象的类型”?
反射方法GetType()是Sytem.Object中被实现的方法,并且不是虚函数。这就是说,任何类都不可能提供对其重写,任何实例调用GetType(),最后都会调用其基类Sytem.Object的GetType(),而GetType()的作用就是获得一开始 调用这个方法的实例的类型。因此,这段代码中无论是"base.GetType()",还是"this.GetType()",其实结果是一致的。"base.GetType()"写法其实更加规范(不愧是反编译出来的==),因为如上所说,GetType()是Sytem.Object中的方法。这里是题外话,另一篇博文会结合实例介绍GetType()的特点。
(3) 通过CanCompareBits是否能进行bit 比较,可以的话采用FastEqualsCheck进行比较。这两个方法都是System.ValueType的私有方法。关于他们的解释,我直接引用博文 Magic behind ValueType.Equals 中的话
The comment of CanCompareBits says "Return true if the valuetype does not contain pointer and is tightly packed". And FastEqualsCheck uses "memcmp" to speed up the comparison.
(4) 在(2)中我们已经得到this所属的type,然后再通过反射获取这个type所有的field,接着分别获取 this对象和比较对象obj在每个field的值,调用equals函数比较每一个值。如果需要用到这一步,时间上的开销就比较大了。
结论
(1) 如果我们自定义一个值类型而不用override关键字去新写一个equals方法,那么当需要比较时:(1) 值类型会被装箱 (2) 装箱后可能还需要调用反射获取每一个field。
所以在在《Effective C#》, 才会有这样一句话:"Always create an override of ValueType.Equals() whenever you create a value type"。
如果打开Int32,Boolean等的定义,可以看到里面都有对Equals()的显示实现,并且都是实现了System.IComparable 接口。
(2) 现在我也可以回答一开篇的疑问了,当我们定义两个int类型的数,然后通过"=="比较它们的时候,系统的做法是:a. 通过"=="重载的内容,发现是调用Equals()进行比较 b.因为Equals已经被Override关键字定义过,直接调用本地定义的Equals()函数。
知道了这些,我们不难知道为什么C#程序员要拥有下面这两个编程习惯:
(1) 在引用类型之间的比较时,不要使用 "==",而应该使用类型自身的Equals() (当然在此之前记得先override这个方法)。如果想判断两个引用类型的对象是否是同一个对象,使用Object.ReferenceEquals()静态方法。
(2) 在系统自带的值类型之间的比较时,可以使用类型自身的Equals(),但是为了可读性,往往使用 "==" 运算符。
相关阅读:
[C#] 类型学习笔记二:详解对象之间的比较的更多相关文章
- JavaScript学习笔记-实例详解-类(二)
实例详解-类(二) //===给Object.prototype添加只读\不可枚举\不可配置的属性objectId(function(){ Object.defineProperty(Object ...
- JavaScript学习笔记-实例详解-类(一)
实例详解-类(一): //每个javascript函数(除了bind())都自动拥有一个prototype对象// 在未添加属性或重写prototype对象之前,它只包含唯一一个不可枚举属性const ...
- Angular6 学习笔记——组件详解之组件通讯
angular6.x系列的学习笔记记录,仍在不断完善中,学习地址: https://www.angular.cn/guide/template-syntax http://www.ngfans.net ...
- Angular6 学习笔记——组件详解之模板语法
angular6.x系列的学习笔记记录,仍在不断完善中,学习地址: https://www.angular.cn/guide/template-syntax http://www.ngfans.net ...
- Angular6 学习笔记——路由详解
angular6.x系列的学习笔记记录,仍在不断完善中,学习地址: https://www.angular.cn/guide/template-syntax http://www.ngfans.net ...
- Android学习笔记-Dialog详解
1.对话框的使用 1.1AlertDialog的显示 简单对话框以及监听的设置:重点掌握三个按钮(也就是三上单词): PositiveButton(确认按钮);NeutralButton(忽略按钮) ...
- [CSS3] 学习笔记-选择器详解(二)
1.选择器first-child.last-child.nth-child和nth-last-child 利用first-child.last-child.nth-child和nth-last-chi ...
- 【转载】自定义View学习笔记之详解onMeasure
网上对自定义View总结的文章都很多,但是自己还是写一篇,好记性不如多敲字! 其实自定义View就是三大流程,onMeasure.onLayout.onDraw.看名字就知道,onMeasure是用来 ...
- C++并发与多线程学习笔记--unique_lock详解
unique_lock 取代lock_quard unique_lock 的第二个参数 std::adopt_lock std::try_to_lock std::defer_lock unique_ ...
随机推荐
- [ Continuously Update ] The Paper List of Image / Video Captioning
Papers Published in 2018 Convolutional Image Captioning - Jyoti Aneja et al., CVPR 2018 - [ Paper Re ...
- CSS基础小记
2017/10/29 CSS 认识CSS样式 CSS全称为"层叠样式表 (Cascading Style Sheets)",它主要是用于定义HTML内容在浏览器内的显示样式,如文字 ...
- a3
队名 massivehard 组员一(组长:晓辉) 今天完成了哪些任务 .整理昨天的两个功能,补些bug 写了一个初步的loyaut 还剩哪些任务: 后台的用来处理自然语言的服务器还没架. 推荐算法还 ...
- python学习笔记06:操作文件
调用内置的open函数打开文件,传递两个参数:文件路径(绝对路径或相对路径),打开模式('r':读,'r+':读写,'w':写,'b':二进制): f = open('data.txt','w') f ...
- YaoLingJump开发者日志(九)V1.1.1版本完成
跳跃吧瑶玲下载连接 百度网盘下载 介绍 Android版本升级到8.0,发现原来的图标不显示了: 百度了一下解决方案,用了该博客提供的第二种方法,修改roundIcon的内容. 可爱的图标 ...
- django 使用json.dumps转换queryset的datatime报错问题解决
最近在使用django做项目的时候想使用ajax来实现前后台数据的交互,但是在将数据库查询结果转换成json数据时,遇到时间格式的数据转换遇到问题,无法正确的进行转换,具体如下: 转换成json时使用 ...
- Jmeter系列-webdriver代码范例
范例 WDS.sampleResult.sampleStart() try{ //打开博客首页 WDS.browser.get('http://xqtesting.blog.51cto.com') / ...
- QT分析之网络编程
原文地址:http://blog.163.com/net_worm/blog/static/127702419201002842553382/ 首先对Windows下的网络编程总结一下: 如果是服务器 ...
- java 基本--数据类型转换--001
小可转大,大转小可能会损失精度(编译出错,需要强制转换)A: byte,short,char -> int -> long -> float ->doubleB: byte,s ...
- 数据包从tcp->ip发出去
ip_local_out->OUTPUT->dst_out->ip_output-> POSTROUTING -->ip_output_finish 上面的路径中啊,在O ...