C#类型基础Part2-对象判等
C#类型基础Part2-对象判等
- 参考资料
- 引用类型判等
- 简单值类型判等
- 复杂值类型判等
参考资料
- 《.NET之美-.NET关键技术深入解析》
引用类型判等
先定义两个类型,它们代表直线上的一个点,一个是引用类型class,一个是值类型struct
public class RefPoint{public int x:public RefPoint(int x){this.x=x;}
}public class ValPoint{public int x:public ValPoint(int x){this.x=x;}
}
在System.Object基类型中,定义了实例方法Equals(Object obj)
,静态方法Equals(Object objA,Object objB)
,静态方法
ReferenceEquals(Object objA,Object objB)
这三个方法来进行对象的判等。
这三个方法实现如下:
public static bool ReferenceEquals (Object objA, Object objB)
{return objA == objB; // #1
}
public virtual bool Equals(Object obj)
{return InternalEquals(this, obj); // #2
}
public static bool Equals(Object objA, Object objB)
{if (objA==objB) { // #3return true;}if (objA==null || objB==null) {return false;}return objA.Equals(objB); // #4
}
先看ReferenceEquals(Object objA,Object objB)方法,它实际上简单地返回
objA==objB。再观察一下Object.Equals()静态方法,如果任何一个对象引用为null,则总是
返回false。当对象不为null时,最后调用了实例上的Equals()方法(#4)。
下面一段代码:
// 复制对象引用
bool result;
RefPoint rPoint1 = new RefPoint(1);
RefPoint rPoint2 = rPoint1;
result = (rPoint1 == rPoint2); // 返回 true;
Console.WriteLine(result);
result = rPoint1.Equals(rPoint2); // #2 返回true;
Console.WriteLine(result);
在这段代码中,在堆上创建了一个新的RefPoint类型的对象实例,并将它的x字段初始化为1;在栈上创建RefPoint类型的变量rPoint1,rPoint1保存了堆上这个对象的地址;而将rPoint1赋值给rPoint2时,此时并没有在堆上创建一个新的对象,而是将之前创建的对象的地址复制到了rPoint2。此时,rPoint1和
rPoint2指向了堆上同一个对象。
从ReferenceEquals()这个方法名就可以看出,它判断两个引用变量是不是指向了同一个变量,如果是,那么就返回true。这种相等叫做引用相等(rPoint1==rPoint2等效于ReferenceEquals)。因为它们指向的是同一个对象,所以对rPoint1的操作将会影响rPoint2。
第二种情况:
//创建新引用类型的对象,其成员的值相等
RefPoint rPoint1 = new RefPoint(1);
RefPoint rPoint2 = new RefPoint(1);
result = (rPoint1 == rPoint2);
Console.WriteLine(result); // 返回 false;
result = rPoint1.Equals(rPoint2);
Console.WriteLine(result); // #2 返回false
上面的代码在堆上创建了两个类型实例,并用同样的值初始化它们;然后将它们的地址分别赋给栈上的变量rPoint1和rPoint2。此时#2返回了false,可以看到,对于引用类型,即使类型的实例(对象)包含的值相等,如果变量指向的是不同的对象,那么也不相等。
简单值类型判等
注意本节的标题:简单值类型判等,这个简单是如何定义的呢?如果值类型的成员仅包含值类型,那么暂且管它叫简单值类型;如果值类型的成员包含引用类型,则管它叫复杂值类型。
值类型都会隐式地继承自System.ValueType类型,而ValueType类型覆盖了基类System.Object类型的Equals()方法,在值类型上调用Equals()方法,会调用ValueType的Equals()。所以,先看看这个方法是什么样的,依然用#number标识后面会引用的地方。
public override bool Equals (Object obj) {if (null==obj) {return false;}RuntimeType thisType = (RuntimeType)this.GetType();RuntimeType thatType = (RuntimeType)obj.GetType();if (thatType!=thisType) { // 如果两个对象不是一个类型,直接返回falsereturn false;}Object thisObj = (Object)this;Object thisResult, thatResult;if (CanCompareBits(this)) // #5return FastEqualsCheck(thisObj, obj); // #6// 利用反射获取值类型所有字段FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance |BindingFlags.Public | BindingFlags.NonPublic);// 遍历字段,进行字段对字段比较for (int i=0; i<thisFields.Length; i++) {thisResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(thisObj,false);thatResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(obj, false);if (thisResult == null) {if (thatResult != null)return false;}else if (!thisResult.Equals(thatResult)) { // #7return false;}}return true;
}
先来看下第一段代码:
// 复制结构变量
ValPoint vPoint1 = new ValPoint(1);
ValPoint vPoint2 = vPoint1;
result = (vPoint1 == vPoint2); //编译错误:不能在ValPoint上应用 "==" 操作符
Console.WriteLine(result);
result = Object.ReferenceEquals(vPoint1, vPoint2); // 隐式装箱,指向了堆上的不同对象
Console.WriteLine(result); // 返回false
上面的代码先在栈上创建了一个变量vPoint1,由于ValPoint是结构类型,因此变量本身已经包含了所有字段和数据。然后在栈上复制了vPoint1的一份副本给了vPoint2。如果依照前面的惯性思维去考虑,那么就会认为它们应该是相等的。然而,接下来试着去比较它们,就会看到,不能用“==” 直接去判断,这样会返回一个编译错误“不能在ValPoint上应用==操作符”。
如果调用System.Object基类的静态方法ReferenceEquals(),就会发生有意思的事情:它返回了false。为什么呢?看下ReferenceEquals()方法的签名就可以了,它接受的是Object类型,也就是引用类型,而当传递vPoint1和vPoint2这两个值类型的时候,会进行一个隐式的装箱,效果相当于下面的语句:
Object boxPoint1 = vPoint1;
Object boxPoint2 = vPoint2;
result = (boxPoint1 == boxPoint2); // 返回false
Console.WriteLine(result)
装箱的过程,在前面已经讲述过,上面的操作等于在堆上创建了两个对象,对象包含的内容相同,但对象所在的地址不同。最后将对象地址分别返回给堆栈上的boxPoint1和boxPoint2变量,再去比较boxPoint1和boxPoint2是否指向同一个对象,显然不是了,所以返回了false。
继续示例程序,添加下面这段代码:
result = vPoint1.Equals(vPoint2); // #5 返回true; #6 返回true;
Console.WriteLine(result); // 输出true
因为它们均继承自ValueType类型,所以此时会调用ValueType上的Equals()方法,在方法体内部,#5处的CanCompareBits(this) 返回了true。CanCompareBits(this)这个方法,按微软的注释,意思是说:如果对象的成员中存在对于堆上的引用,那么返回false,如果不存在,返回true。按照ValPoint的定义,它仅包含一个int类型的字段x,自然不存在对堆上其他对象的引用,所以返回了true。从#5处的名字CanCompareBits可以看出,是在判断是否可以进行按位比较,因此返回了true以后,#6自然是进行按位比较了。
接下来,对vPoint2做点改动,看看会发生什么:
vPoint2.x = 2;
result = vPoint1.Equals(vPoint2); // #5 返回true; #6 返回false;
Console.WriteLine(result);
此时,因为vPoint2中的int值发生了变化,所以在#6处按位比较时,就会返回false。
复杂值类型判等
到现在为止,上面的System.ValueType.Equals()方法,还没有执行到的位置,就是CanCompareBits返回false以后的部分了。前面已经推算出了CanCompareBits返回false的条件(值类型的成员包含引用类型),现在只要实现一下就可以了。重新定义一个新的结构ValLine,它代表直线上的线段,让它的一个成员为值类ValPoint,一个成员为引用类型RefPoint,然后去作比较。
/* 结构类型 ValLine 的定义,
public struct ValLine {public RefPoint rPoint; // 引用类型成员public ValPoint vPoint; // 值类型成员public Line(RefPoint rPoint, ValPoint vPoint) {this.rPoint = rPoint;this.vPoint = vPoint;}
}
*/
RefPoint rPoint = new RefPoint(1);
ValPoint vPoint = new ValPoint(1);
ValLine line1 = new ValLine (rPoint, vPoint);
ValLine line2 = line1;
result = line1.Equals(line2); // 此时已经存在一个装箱操作,调用ValueType.Equals()
Console.WriteLine(result); // 返回True
这个例子的过程要复杂得多。在开始前,先思考一下,当写下line1.Equals(line2)时,已经进行了一个装箱的操作。如果要进一步判等,显然不能去判断变量是否引用了堆上同一个对象,这样就没有意义了,因为总是会返回false(装箱后堆上创建了两个对象)。那么应该如何判断呢?对堆上对象的成员(字段)进行一对一的比较,而成员又分为两种类型,一种是值类型,一种是引用类型。对于引用类型,去判断是否引用相等;对于值类型,如果是简单值类型,那么同前一节讲述的一样去判断;如果是复杂类型,那么当然是递归调用了;
最终确定要么是引用类型要么是简单值类型。
好了,现在看看实际的过程,是不是如同我们所料想的那样,为了避免频繁地拖动滚动条查看ValueType的Equals()方法,这里将代码复制了部分:
public override bool Equals (Object obj) {//前面略if (CanCompareBits(this)) // #5return FastEqualsCheck(thisObj, obj); // #6// 利用反射获取类型的所有字段(或者叫类型成员)FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance |BindingFlags.Public | BindingFlags.NonPublic);// 遍历字段进行比较for (int i=0; i<thisFields.Length; i++) {thisResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(thisObj,false);thatResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(obj, false);if (thisResult == null) {if (thatResult != null)return false;}else if (!thisResult.Equals(thatResult)) { #7return false;}}return true;
}
- 进入ValueType上的Equals()方法,#5处返回了false;。
- 进入for循环,遍历字段。
- 第一个字段是RefPoint引用类型,#7处调用System.Object的Equals()方法,到达#2,返回true。
- 第二个字段是ValPoint值类型,#7处调用System.ValType的Equals()方法,也就是当前方法本身。注意此处是递归调用。
- 再次进入ValueType的Equals()方法,因为ValPoint为简单值类型,所以#5处的CanCompareBits返回了true,接着#6处的FastEqualsCheck返回了true。
- 里层Equals()方法返回true。
- 退出for循环。
- 外层Equals() 方法返回true
相关文章:
C#类型基础Part2-对象判等
C#类型基础Part2-对象判等 参考资料引用类型判等简单值类型判等复杂值类型判等 参考资料 《.NET之美-.NET关键技术深入解析》 引用类型判等 先定义两个类型,它们代表直线上的一个点,一个是引用类型class,一个是值类型struct public class…...
13.CSS 打印样式表 悬停下划线动画
CSS 打印样式表 虽然我们不经常从网上实际打印内容,但打印样式表不应被忽视。它们可以用来确保你的网站内容以一种易读和适合打印的方式呈现。这里有一个简单的、独特的打印样式表,你可以用它作为自己的基础: media print {page {size: A4;}body {margin: 0;padding: 0;}body, …...
C#基础:数据库分表的好处和实现方式
一、分表的好处: 1.提升查询速度:分表筛选后再拼接,而不是查大表,速度会显著提升 2.管理容易:根据业务需求,通常会按照时间或者空间来分表 3.提高并发性:降低锁竞争和查询阻塞的风险…...

基于3D开发引擎HOOPS平台的大型三维PLM系统的设计、开发与应用
产品生命周期管理(Product Lifecycle Management,PLM)系统在现代制造业中扮演着至关重要的角色。随着工业4.0和智能制造的推进,PLM系统从最初的CAD和PDM系统发展到现在的全面集成、协作和智能化的平台。本文将探讨基于HOOPS平台的…...

学习React(描述 UI)
React 是一个用于构建用户界面(UI)的 JavaScript 库,用户界面由按钮、文本和图像等小单元内容构建而成。React 帮助你把它们组合成可重用、可嵌套的 组件。从 web 端网站到移动端应用,屏幕上的所有内容都可以被分解成组件。在本章…...

mysql字符类型字段设置默认值为当前时间
-- 2024-07-22 10:22:20 select (DATE_FORMAT(CURRENT_TIMESTAMP, %Y-%m-%d %H:%i:%s)); ALTER TABLE tablename MODIFY COLUNN CREATE_DATE varchar (23) DEFAULT(DATE_FORMAT(CURRENT_TIMESTAMP, %Y-%m-%d %H:%i:%s)) COMMENT "创建日期;...

java题目之数字加密以及如何解密
public class Main6 {public static void main(String[] args) {// 某系统的数字密码(大于0),比如1983,采用加密方式进行传输//定义了一个静态数组int []arr{1,9,8,3};//1.加密//先给每位数加上5for (int i 0; i <arr.length …...

Linux基于CentOS7【yum】【vim】的基础学习,【普通用户提权】
目录 yum生态 什么是yum yum是如何得知目标服务器的地址和下载链接 vim vim模式 命名模式 光标移动 插入模式 i键插 a键插 o键插 底行模式 批量化注释 批量化去注释 创建vim配置文件 例子 高亮功能: 缩进功能: 符号位自动补齐功能…...

盛元广通实验室自动化生物样本库质量控制管理系统
随着我国生物医学研究的不断深入和精准医疗的快速发展,对高质量生物样本的需求日益增长。近年来,我国生物样本库建设取得了显著进展。各级政府、高校和医院纷纷投入资源建设生物样本库,推动了生物样本资源的有效整合和利用。生物样本库的质量…...

Java | 自制AWT单词猜一猜小游戏(测试版)
目录 游戏标题 开发过程 开发想法 技术栈 代码呈现 导包 核心代码 游戏标题 探索知识的迷宫,体验自制AWT单词猜一猜小游戏 在数字时代,学习可以是多彩的,游戏可以是智慧的。我们自豪地推出“单词猜猜猜”是一款结合了教育与娱乐的自制…...

docker搭建ES 8.14 集群
参考:【docker搭建es8集群kibana】_docker 安装生产级 es 8.14 集群-CSDN博客 1、之前已搭建一台单机版的dockerES集群 参见 Elasticsearch docker 安装_docker 安装es8.14.3-CSDN博客 2、现在需要重新搭建为docker ES集群 准备新搭建3个点 一、准备工作 提前开…...
自定义特征的智能演进:Mojo模型中的动态特征选择控制
自定义特征的智能演进:Mojo模型中的动态特征选择控制 在机器学习领域,特征选择是提升模型性能和泛化能力的关键步骤。Mojo模型,作为一种高效的模型部署方式,其对特征的动态选择和控制能力是实现高级机器学习应用的重要特性。本文…...
Git->Git生成patch和使用patch
生成patch git format-patch -1 HEAD -o "输出目录"format-patch:用于生成补丁文件-1:-1 表示最近一次提交,-2 表示生成最近两次提交的补丁。HEAD:HEAD 指向当前分支的最新提交-o:指定生成的补丁文件的输出…...
开发面试算法题求教
在《无尽的拉格朗日》中,有许多不同的星系建筑物。每个星系建筑物的等级不同,带来的影响力也不同。 已知宇宙可以抽象为一个无穷大的平面直角坐标系,现在给定了每个星系建筑物的所在坐标(xi,yi)和它的影响力ri,距离其切比雪夫距离…...
OpenStack中nova的架构
1.1 nova-api 负责接收和相应客户的API调用。 1.2 compute core nova-schedule 负责决定在哪个计算节点运行虚拟机。 nova-compute 通过调用Hypervisor实现虚拟机生命周期的管理。一般运行在计算节点。 hypervisor 对虚拟机进行硬件虚拟化的管理软件ÿ…...

力扣高频SQL 50题(基础版)第五题
文章目录 力扣高频SQL 50题(基础版)第五题1683. 无效的推文题目说明:思路分析:实现过程:结果截图: 力扣高频SQL 50题(基础版)第五题 1683. 无效的推文 题目说明: 表&a…...

Air780EP- AT开发-阿里云应用指南
简介 使用AT方式连接阿里云分为一机一密和一型一密两种方式,其中一机一密又包括HTTP认证二次连接和MQTT直连两种方式 关联文档和使用工具: AT固件获取在线加/解密工具阿里云平台 准备工作 Air780EP_全IO开发板一套,包括天线SIM卡࿰…...

【中项】系统集成项目管理工程师-第4章 信息系统架构-4.4数据架构
前言:系统集成项目管理工程师专业,现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试,全称为“全国计算机与软件专业技术资格(水平)考试”&…...

excel批量新建多个同类型的表格
背景引入 比如,一个企业有多个部门,现在需要按照某一个excel表模板收集各个部门的信息,需要创建数十个同类型表格,且标题要包含部门名称。 1.修改模板表格标题 在一个文件夹下面放入需要发放给各个部门的表格,将标题…...

React Native 与 Flutter:你的应用该如何选择?
Flutter 和 React Native 都被认为是混合应用程序开发中的热门技术。然而,当谈到为你的项目使用框架时,你必须考虑哪一个是最好的:Flutter 还是 React Native? 本篇文章包含 Flutter 和 React Native 在各个方面的差异。因此&…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...

视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

密码学基础——SM4算法
博客主页:christine-rr-CSDN博客 专栏主页:密码学 📌 【今日更新】📌 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 编辑…...