C# - 为引用类型重定义相等性 - 继承相关
派生类
这是上面Citizen类的一个子类:
下面我重写object.Equals() 方法:
大部分逻辑都在base.Equals()方法里了,首先如果父类的Equals()方法返回false,那么下面也就不用做啥了。但是如果父类Equals()认为这两个实例是相等的,这就意味着父类里所有的相等性检查都通过了,然后我们仍然需要检查派生类里面的独有字段(属性),而这个例子里只有一个字段(属性)。
然后别忘了实现GetHashCode()方法:
(resharper生成的代码)
这个方法里使用了父类的GetHashCode()方法,把它按位异或IdCard的GetHashCode()的结果。
然后实现==和!=操作符:
好,现在我们来测试一下:
其结果如下:
这个结果还都是对值进行比较的,符合预期。
然后你可能以为这样实现没有问题了。。。。
陷阱
现在我在Citizen这个父类里修改一下==的实现,我想让它更有效率:
然后我再执行和上面同样的测试代码,其结果输入是:
?,全都相等了。。。。肯定不对。。
那在父类里的==方法设一下断点看看:
这里面x和y其实都是BeijingCitizen的实例,但是现在所处的位置是其父类Citizen的==方法里,所以相等性检查会在这里发生,所以这个相等性检查只会检查父类里面的字段,Citizen这个类无法知道其它继承于它的类型,所以这里也无法比较派生类独有的字段,在这里就是IdCard。而所有这些实例的不同值就去别再IdCard这个派生类的字段上面了,所以所有检查的结果都是相等的,因为只比较了父类的那两个字段。
为什么会调用Citizen父类的==方法呢?因为该方法是静态的,也就不是virtual的。而我的测试代码:
其参数类型是父类Citizen,所以a==b这句话会在编译时就决定采取哪个版本的==实现,而编译器在这个方法里会看到a和b的类型都是Citizen,所以它会调用Citizen版本的==实现。
所以这确实是一个陷阱。
但是为什么原来的写法就没有问题呢?
原来的写法里,在Citizen这个父类里,==的实现调用了 object的静态Equals()方法,而在这个静态Equals方法里:
又调用了object的virtual Equals()方法,而如果实际类型是BeijingCitizen的话,那么就会调用override的Equals()方法,我们单独看这个比较:
在BeijingCitizen里设一个断点:
可以看到会击中该断点。也可以看一下CallStack:
现在再次运行所有测试,其结果:
就是正确的了。
所以说,相等性检查的逻辑需要放在virtual的方法里。
如果再往上一级,把参数都变成object类型:
输出结果是:
这是因为==的实现不是virtual的,在object类型上使用==就是判断引用的相等性。而你也无法在重载操作符来防止上述事情的发生,因为这段代码永远不会调用到你的操作符重载方法。
那么结论就是,在操作符重载方法里调用vitual的方法,就可以应付继承相关的相等性判断,但是至少也得输入你定义的父类的类型(Citizen),好让你定义的操作符重载方法可以被最先调用。如果要满足继承、相等性这两方面的要求,那么就需要牺牲类型安全:
所以==操作符重载,可以看作一种方便的语法糖法,同时也把类型不安全的Equals()方法包装了起来。
为什么不实现IEquatable<T>
如果我在Citizen类里面实现了该接口:
那么方法里的调用也还是调用virtual的Equals(),否则的话还是一样的bug。那么这样看的话,实现该接口几乎没有什么新鲜的作用,虽然说该方法可以做到一定程度的类型安全,但是性能上,比直接调用object.Equals()更慢了。
所以针对引用类型,不建议实现IEquatable<T>接口。
非得实现的话建议sealed
例如:
这样的话,我们就可以把判断相等的逻辑写在该方法里了,因为这个类是sealed,所以能传递到这个方法里的变量一定是该类型的,没有继承的存在,我们就可以同时拥有类型安全和相等性了。
为sealed的class实现IEquatable<T>接口肯定是可行的,但是否值得呢?
优点:能得到微小的性能提升,string就是个例子。
缺点:class本身就更复杂了,你需要记住3种实现相等性判断的方式。。。
综上个人建议是针对引用类型不去实现IEquatable<T>接口。
.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com
C# - 为引用类型重定义相等性 - 继承相关相关推荐
- 继承和多态 1.0 -- 继承概念(is-a、has-a,赋值兼容规则,隐藏重定义)
普通继承和访问权限 当一个继承没有虚拟继承或者是多重继承时,就是一个简单的继承的时候,这个时候就是一个普通的继承. 普通继承的内存空间是:子类的对象中,包含了父类的成员变量,同时也可以调用父类的成员函 ...
- 第九天2017/04/18(3、重载/覆盖 PK 重写/重定义、父类子类混搭风、抽象类)
1.重载/覆盖 PK 重写/重定义 [预备知识] 函数重载必须在同一个类中发生子类无法重载父类的函数,父类同名的函数将会被名称覆盖重载是在编译期间根据参数类型和个数决定函数调用重载只放在同一个类之中, ...
- c++ 重载、重写、重定义(隐藏)
1.重载overload:函数名相同,参数列表不同. 重载只是在类的内部存在,或者同为全局范围.(同名,同参函数返回值不同时,会编译出错.因为系统无法知晓你到底要调用哪一个.) 2.重写overrid ...
- C++中的覆盖(重写)、重载、隐藏(重定义)、多态!
例一: 首先声明Base类型的指针指向实际类型为Derived的对象,先调用基类构造函数,再调用派生类构造函数.输出Base, Derived. base->echo(); 指针是base类型, ...
- 【C++grammar】名字隐藏与重定义
目录 1.继承中的名字隐藏 1.基类同名函数被隐藏的现象描述 2.问题理解 3.避免现象 2.重定义 1.现象描述 2.重定义与重载的区别 3.能否使用 using 将基类成员引入到派生类定义中 1. ...
- c++中的多态---1(多态概念,静态联编和动态联编,多态原理解析,重载,重写,重定义的对比)
多态的基本概念 多态是面向对象设计语言数据抽象和继承之外的第三个基本特征 多态性(polymorphism)提供接口与具体实现之间的另一层隔膜,从而将"what"和"ho ...
- 初识C++之函数重载、重写、重定义的区别
在C++的学习中,慢慢接触了一些很容易混淆的名词,今天就来剖析几个容易混淆的名词. 1.函数重载 重载函数是函数的一种特殊情况,为方便使用,C++允许在同一范围中声明几个功能类似的同名函数,但是这些同 ...
- 华为发布智能数据解决方案FusionData,重定义数据基础设施,释放数据价值
[中国,北京,2019年6月5日]华为在北京发布智能数据解决方案FusionData,支持智能的数据全生命周期管理:从数据接入.数据处理和数据使能三个层面,重定义数据基础设施,帮助客户打造领先的智能数 ...
- oracle 10g在线重定义新特性——关联对象自动重命名(二)
9i的在线重定义存在一个问题,执行完在线重定义后,表的名称虽然保持不变,但是索引.约束.触发器等关联对象的名称会发生变化,有时候这会带来一定的问题,而要在事后手工修改,会比较麻烦. 10g的在线重定义 ...
最新文章
- php 安装rabbitmq拓展_安装 php-rabbit: RabbitMQ 的 PHP 扩展
- webview部分安卓机中文乱码
- antd 表单域验证规则 - 只能输入数字字符,去除前导0
- Console-算法[for]-国王与老人的六十四格
- python numpy库是第三方库吗_浅谈python的第三方库——numpy(终)
- hibernate之多对多关联映射
- Jmeter 抓app包 抓到一半不好用了
- 无字库12864液晶屏滚动显示程序[转]
- 走不远的共享滑板车!
- (转)WriteOnce and RunAnyWhere
- MockingBot for Mac(原型设计协同插件)sketch插件
- Python 各种画图
- AT070TN83 V1.0 背光不亮
- PMP-32项目成本管理
- python多变量相关性分析_两个变量与因变量相关性分析_spss多变量相关性分析
- 计算机网络 网络安全问题概述
- 廊坊金彩教育:店铺详情页设计要点
- Raspberry 2B Ubuntu mate 16.04 *** 完美透明代理
- 网络性能衡量指标总结
- Linux基础系统优化及常用命令
热门文章
- Vuejs——组件——slot内容分发
- redis 安装错误 jemalloc.h: No such file or directory
- 编写iptables脚本实现IP地址、端口过滤
- Win10系统修改MAC地址
- 开源Math.NET基础数学类库使用(04)C#解析Matrix Marke数据格式
- 第十周项目1-程序填充与阅读(三)
- Mysql 常用函数总结
- 当 dotnet-monitor 遇上 Prometheus, 是种什么样的体验?
- 技术分享 | 一条神奇的曲线——贝塞尔曲线在前端的应用
- 如何评价一个开源项目——价值流网络