相等性 比较【ReferenceEquals、静态Equals、==(ceq)、实例eEquals】
感觉 最近学习学疯了,突然对以前熟悉的东西感到陌生。然后又回头重新挖掘一下
什么是相等性呢?以前一直用== 默认是值相等,从未去考虑,是地址相等还值相等。今天就详细的研究一下。
.net 平台提供了4中相等性比较符。ReferenceEquals、静态Equals、实例eEquals、==(“ceq”MSIL 命令 )
ceq的意思是compare for equality。针对原始类型,C#的==操作符并没有使用.NET里提供的那些Equals方法,这时==操作符使用专用的汇编语言指令来进行判断相等性的。
比较的效率:ReferenceEquals>
.net 提供了4种等比较方法 3个是object方法可继承, 【ceq】MSIL 操作符,可以重载 |
【object方法】 | 【ceq】MSIL 操作符 | ||
ReferenceEquals | 实例eEquals | 静态Equals | == | |
object | 引用地址 | 值, A.Equals.(B)要求实例A不能是null,B实例可以是null,值类型比的是值,引用类型比的是地址 |
值 object.Equals.(A,B)2个实例都可以是null。值类型比的是值,引用类型比的是地址 |
引用地址 |
string | 继承object,注意拘留池 不可变变对引用地址的隐形 |
值:5重载'== | 继承object | 值:重载'== |
Tuple | 继承object | 值:重载 | 继承object | 引用地址 |
Delegate | 继承object | 重载 若两个运行时间类型相同的委托操作数均为 null 比较委托列表地址和排序: |
继承object | 若两个运行时间类型相同的委托操作数均为 null 比较委托列表地址和排序:重载 |
接口 | 继承object | 继承object | 继承object | 引用地址 |
自定义class | 继承object | 继承object | 继承object | 引用地址 |
泛型 | 继承object | 引用地址 | 继承object | 限制为 Where T:class 才能在内部使用 引用地址 因为传入也有可能是struct类型,不能用== |
记录类型 | 继承object | 值: 当两个记录操作数均为 null 或所有字段的对应值和自动实现的属性相等时,两个记录操作数都相等对于记录,编译器将生成 Equals 方法 |
继承object | 值:重载'== 当两个记录操作数均为 null 或所有字段的对应值和自动实现的属性相等时,两个记录操作数都相等 |
内置值类型:struct | 继承object不要用,因为装箱会导致引用地址不一样。 | 值:重载 | 继承object | 值 |
自定义 ValueTuple:struct |
继承object不要用,因为装箱会导致引用地址不一样。 | 值:ValueType重载了Object的Equals()方法。当比较两个值类型变量是否相等时,可以调用继承自ValueType类型的Equals()方法。这个重载的方法内部使用了反射,获得值类型所有的字段,然后进行比较。 对于值类型,应始终重写 Equals ,因为依赖于反射的相等性测试会降低性能。 |
继承object | 值类型默认无法使用 == 操作符,除非对它进行重写 |
Enum:class | 继承object不要用,因为装箱会导致引用地址不一样。 |
值:重载 | 继承object | 值 |
Tuple | 静态类Tuple | |||
struct | 抽象类ValueType | 抽象类的映射,是不能使用抽象类的所有成员,只能用来继承 | ||
enum | 抽象类Enum | 抽象类的映射,是不能使用抽象类的所有成员,只能用来继承 | ||
delegate | 抽象类 MulticastDelegate | 抽象类的映射,是不能使用抽象类的所有成员,只能用来继承 | ||
string | 密封类String | 重载== | ||
long | 只读结构体Int64 | |||
ulong | 只读结构体UInt64 | |||
int | 只读结构体Int32 | |||
uint | 只读结构体UInt32 | |||
short | 只读结构体Int16 | |||
ushort | 只读结构体UInt16 | |||
byte | 只读结构体Byte | |||
sbyte | 只读结构体Sbyte | |||
nint | 只读结构体IntPtr | |||
nuint | 只读结构体UIntPtr | |||
float | 只读结构体Single | 重载== | ||
double | 只读结构体Double | 重载== | ||
decimal | 只读结构体Decimal | 重载== | ||
char | 只读结构体Char | |||
bool | 只读结构体Boolean | |||
ValueTuple | 结构体ValueTuple | |||
可空类型 | 静态类Nullable | |||
object相等性 比较
对这4方法的研究我们从object开始。
object 并未重载==,所以在object使用==相等的比较中,object是对堆栈的中引用地址进行比较。
【ReferenceEquals】是引用类型的引用地址相等比较,只能在引用类型比较使用。值类型因为会涉及到装箱后地址会不相同,所以对比的结果都是fase;
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[System.Runtime.Versioning.NonVersionable]
public static bool ReferenceEquals (Object objA, Object objB) {
return objA == objB;
}
【静态Equals】对值进行比较
public static void Equals( object left, object right )
{
//这一步是是引用类型比较。值类型装箱后地址不一样需要进一步判断
if( left == right )
return true;//引用类型比较之间对比堆栈中的引用地址,
// both null references handled above
if( ( left == null ) || ( right == null ) )
return false;
return left.Equals( right );//这一步是判断值类型的,最终也是引用RuntimeHelpers.Equals }
【==】在object中没有重载==,object在堆栈保存的是引用地址,所以它进行直接引用比较。
int i = 1,j = 1;
Console.WriteLine( (object)i==(object)j);//false,int装箱后的地址是不一样的
【实例Equals】 是比较值相等。
public virtual bool Equals(Object obj)
{
return RuntimeHelpers.Equals(this, obj);
}
我们通过object的源代码就可以知道他们之间的差别了。
在object 中==和ReferenceEquals功能是一样的都是 比较地址是否相等。【静态Equals】判断的是值或者引用地址相等就相等。【实例Equals】判断的是值相等。
因此我在自定义 值类型时候要重载==,把他重载成值比较;在自定义引用类型的时候要重载Equals,把他重载成地址比较。
【== 】在值类型中表示值相等比较,在引用类型中表示引用地址相等比较,
【== 】在值类型的应用: 如果内置值类型的值相等,则其操作数相等:用户定义的 struct 值类型默认情况下不支持 ==
运算符。 要支持 ==
运算符,用户定义的结构必须重载它。重载是【!=】也要一起重载了。
【== 】 在引用类型的应用:默认情况下,如果两个非记录引用类型操作符引用同一对象,则这两个操作符相等:
默认情况下,用户定义的引用类型支持 ==
运算符。 但是,引用类型可重载 ==
运算符。 如果引用类型重载 ==
运算符,使用 Object.ReferenceEquals 方法来检查该类型的两个引用是否引用同一对象。
记录类型相等性:在 C# 9.0 和更高版本中提供,记录类型支持 ==
和 !=
运算符,这些运算符默认提供值相等性语义。 也就是说,当两个记录操作数均为 null
或所有字段的对应值和自动实现的属性相等时,两个记录操作数都相等。
委托相等性:1、如果两个委托实例中有一个为 null,则当且仅当它们都为 null 时相等。
2、 具有不同运行时类型的委托永远不相等。
3、如果两个委托实例都具有调用列表,则当且仅当它们的调用列表长度相同,并且一个实例的调用列表中的每项依次等于(如下面的定义)另一个的调用列表中的相应项时,这两个委托实例相等。
以下规则控制调用列表项的相等性:
如果两个调用列表项都引用同一静态方法,则这两项相等
如果两个调用列表项都引用同一个目标对象(引用相等运算符定义的目标对象)上的同一个非静态方法,则这两个调用列表项相等
(这句话不理解) 通过对语义相同的匿名_方法_表达式或具有相同(可能为空)捕获的外部变量实例集的lambda_表达式进行求值而生成的调用列表项允许(但不是必需)相等。
字符串的相等性比较:字符串string 具有值不变性和拘留池特性。字符串虽然是引用类型,但是相等比较时候(==、equal)是比较两个字符串的值,而不是对比引用地址是否相等。
在作字符串引用地址相等性比较时,要用Object.ReferenceEquals 方法。详细请看:
当 初始化字符串是常量时,运行时就会把常量保存在拘留池中,当遇到相同常量给string赋值时,clr就直接引用拘留池中的常量给string赋值。
string a = "aasdsdsa";
string b = "aasdsdsa";
Console.WriteLine(object.ReferenceEquals(a, b)); //引用地址是相同的
Equals和== 有什么区别?
Equals对于值类型装箱成object类型的比较时,是比较值,==是比较引用地址。
string 相等性 比较
string 重载了==操作符,把==操作符重载成 值相等
public static bool operator ==(String a, String b)
{
return String.Equals(a, b);
} public static bool Equals(String a, String b)
{
if ((Object)a == (Object)b)
{
return true;
} if ((Object)a == null || (Object)b == null)
{
return false;
} if (a.Length != b.Length)
return false; return EqualsHelper(a, b);
}
stirng 重载了object的Equals方法, 将重载成应用值相等
public override bool Equals(Object obj) {
if (this == null) //this is necessary to guard against reverse-pinvokes and
throw new NullReferenceException(); //other callers who do not use the callvirt instruction String str = obj as String;
if (str == null)
return false; if (Object.ReferenceEquals(this, obj))
return true; if (this.Length != str.Length)
return false; return EqualsHelper(this, str);
}
通过以上分析,string中Equals和==都对字符串值进行比较。所以在string中Equals和== 比较结果是一样的
【ReferenceEquals】在string使用这个函数,要注意string 拘留池的特性。运行时会把字符串常量捕获 存入拘留池。当用相同常量时,字符串拘留池就返回已经存在拘留池中字符串的地址。编译器会"x" + "y" + "z"进行运算得到“xyz”,由于拘留池中已经再次
“xyz”,所"x" + "y" + "z"就直接用字符串拘留池中的实例。
下运行结果是true
string s = "xyz";
Console.WriteLine(object.ReferenceEquals("x" + "y" + "z", s));
关语 C#中字符串优化String.Intern、IsInterned详解
值类型
非原始类型
看例子,这里有两个值类型:
当我使用==对它们进行比较的时候,直接报错了。
因为默认情况下,不可以使用==来对非原始类型的值类型进行相等性判断。要想使用==,就必须提供重载方法。
struct
当我们比较两个结构体是否相等时,怎么做呢?因为变量本身包含了结构体所有的字段(数据),所以在比较时,就需要对两个结构体的字段进行逐个的一对一的比较,看看每个字段的值是否都相等,如果任何一个字段的值不等,就返回false。
==不可用,要重载后才可用
实际上,执行这样的一个比较并不需要我们自己编写代码,Microsoft已经为我们提供了实现的方法:所有的值类型继承自System.ValueType,ValueType和所有的类型都继承自System.Object,Object提供了一个Equals()方法,用来判断两个对象是否相等。但是ValueType覆盖了Object的Equals()方法。当我们比较两个值类型变量是否相等时,可以调用继承自ValueType类型的Equals()方法。这个复写的方法内部使用了反射,获得值类型所有的字段,然后进行比较。
static void Main(string[] args)
{
Person per1 = new Person { name = "xiaolin", age = 12 };
Person per2 = new Person { name = "xiaolin", age = 12 }; Console.WriteLine(per1.Equals(per2));
Console.WriteLine(Equals(per1, per2)); } struct Person
{
public int age;
public string name; }
Tuple
针对这两个tuple,我做了三个相等性判断,通过第一个ReferenceEquals方法我们可以知道这两个tuple变量指向不同的实例。
而tp1.Equals(tp2)返回的是True,这是因为Tuple类(引用类型)重写了object.Equals()方法,从而比较的是Tuple里面的值。
尽管微软为Tuple把object.Equals()方法重写了,但是它并没有处理==操作符,所以==还是在比较引用的相等性,所以会返回False。
这样做确实挺让人迷惑的。。。
泛型
另一种不适合使用==操作符的情景是涉及泛型的时候,直接看例子:
static bool ByEqualOperator<T>(T a,T b){ return a==b;
}
这个泛型方法直接报错了,因为==操作符无法应用于这两个操作数T,T可以是任何类型,例如T是非原始类型的struct,那么==就不可用。我们无法为泛型指定约束让其实现某个操作符。针对这个例子,我可以这样做,来保证可以编译:
现在T是引用类型了,代码可以编译了。我们使用以下该方法:
按理说这就相当于调用了Equals()方法,结果应该返回True。而实际结果是:false
之所以返回了False,是因为泛型方法里的==操作符比较的是引用,而这又是因为尽管编译器知道可以把==操作符应用于类型T,但是它仍然不知道具体是哪个类型T会重载该操作符,所以它会假设T不会重载==操作符,从而对待这两个操作数如同object类型一样并编译,所以判断的是引用相等性。
所以泛型方法不会选择任何的操作符重载,它对待泛型类就像对待object类型一样。
综上,针对泛型方法,应该使用Equals()方法,而不是==操作符。
备注:
ceq的意思是compare for equality,就是比较两个值是否相等,在运行时,它将会被转换为硬件上的比较,也许用的是CPU的寄存器
相等性 比较【ReferenceEquals、静态Equals、==(ceq)、实例eEquals】的更多相关文章
- js静态属性,实例属性,封装性,prototype,__proto__综合解析
原创作品,转载请注明来源,sogeisetsu,我的csdn上也有这篇文章csdn js静态属性,实例属性,封装性,prototype,__proto__综合解析 下面是我在写博客的源代码,您可以先不 ...
- [原创]java WEB学习笔记102:Spring学习---Spring Bean配置:bean配置方式(工厂方法(静态工厂方法 & 实例工厂方法)、FactoryBean) 全类名
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- (转)java线程安全问题之静态变量、实例变量、局部变量
java多线程编程中,存在很多线程安全问题,至于什么是线程安全呢,给出一个通俗易懂的概念还是蛮难的,如同<java并发编程实践>中所说: 写道 给线程安全下定义比较困难.存在很多种定义,如 ...
- Java中静态变量与实例变量
知识回顾 上一篇总结了java中成员变量和局部变量的区别,这一篇将总结静态变量和实例变量的一些特性和区别. 示例代码 package Variable; public class VariableDe ...
- java线程安全问题之静态变量、实例变量、局部变量
java多线程编程中,存在很多线程安全问题,至于什么是线程安全呢,给出一个通俗易懂的概念还是蛮难的,如同<java并发编程实践>中所说: 写道 给线程安全下定义比较困难.存在很多种定义,如 ...
- Java静态初始化,实例初始化以及构造方法
首先有三个概念需要了解: 一.静态初始化:是指执行静态初始化块里面的内容. 二.实例初始化:是指执行实例初始化块里面的内容. 三.构造方法:一个名称跟类的名称一样的方法,特殊在于不带返回值. 我们先来 ...
- Java父类与子类中静态代码块 实例代码块 静态变量 实例变量 构造函数执行顺序
实例化子类时,父类与子类中的静态代码块.实例代码块.静态变量.实例变量.构造函数的执行顺序是怎样的? 代码执行的优先级为: firest:静态部分 second:实例化过程 详细顺序为: 1.父类静态 ...
- Java 中静态变量和实例变量区别
Java 中静态变量和实例变量区别 静态变量属于类,该类不生产对象,通过类名就可以调用静态变量. 实例变量属于该类的对象,必须产生该类对象,才能调用实例变量. 在程序运行时的区别: 实例变量属于某个对 ...
- JS Foo.getName笔试题解析,杂谈静态属性与实例属性,变量提升,this指向,new一个函数的过程
壹 ❀ 引 Foo.getName算是一道比较老的面试题了,大致百度了一下在17年就有相关文章在介绍它,遗憾的是我在19年才遇到,比较奇妙的是现在仍有公司会使用这道题.相关解析网上是有的,这里我站在 ...
随机推荐
- 20个 CSS 快速提升技巧
作者:web秀 http://www.javanx.cn/20190321/css-skill/ 本文涵盖了20个css技巧,可以解决许多工作中常见的问题. 1.使用CSS重置(reset) css重 ...
- 深入理解 React 的 Virtual DOM
React在前端界一直很流行,而且学起来也不是很难,只需要学会JSX.理解State和Props,然后就可以愉快的玩耍了,但想要成为React的专家你还需要对React有一些更深入的理解,希望本文对你 ...
- ApacheCN 捐赠名单 2019
这是 ApacheCN 的捐赠名单,不是龙哥盟博客的(关于 ApacheCN). 最新的名单请见 https://home.apachecn.org/donate/. 捐赠者 金额(元) 时间 收入类 ...
- MySql数据存储格式Compact及计算MySql的B+Tree高度
1.MySql的compact行记录格式 MySql从版本5.1以后默认使用的是compact行记录格式.可以通过执行以下命令查询到Row_format知悉InnoDB行记录格式类型. show ta ...
- Java8之Stream常用操作方式
哈喽!大家好,我是[学无止境小奇],一位热爱分享各种技术的博主! [学无止境小奇]的创作宗旨:每一条命令都亲自执行过,每一行代码都实际运行过,每一种方法都真实实践过,每一篇文章都良心制作过. [学无止 ...
- (DDS)正弦波形发生器——幅值、频率、相位可调(一)
(DDS)正弦波形发生器--幅值.频率.相位可调 一.项目任务: 设计一个幅值.频率.相位均可调的正弦波发生器. 频率每次增加1kHz. 相位每次增加 2*PI/256 幅值每次增加两倍 二.文章内容 ...
- JAVA多线程学习五:线程范围内共享变量&ThreadLocal
一.概念 可以将每个线程用到的数据与对应的线程号存放到一个map集合中,使用数据时从这个集合中根据线程号获取对应线程的数据,就可以实现线程范围内共享相同的变量. 二.代码 Runnable中的run( ...
- chmod 权限-rw-r--r--表示什么含义
感谢原文作者:bugcoder321 原文链接:https://blog.csdn.net/li_canhui/article/details/89452134 在linux中,有时候可以看到一个文件 ...
- Jackson中处理map中的null key 或者null value 及实体字段中的null value
1.map中有null key时的序列化 当有null key时,jackson序列化会报 Null key for a Map not allowed in JSON (use a convert ...
- 如何使Label显示时,一行顶部居中,两行靠左显示----董鑫
有时我们会碰到这种情况,一个要根据内容显示一行还是两行,一行时还要靠着顶部再居中,比如下面 最左边的名称,要求是靠上的,如果按照正常的方式写的话,可能一行的话就会出现居中显示了,不会顶着头部显示. 我 ...