NET-知识点:C#中Equals和==比较
第一、相等性比较
其实这个问题的的本质就是C#的相等比较,相等比较可以分两类:
1、引用相等性,引用相等性指两个对象引用均引用同一基础对象。
2、值相等性,值相等性指两个对象包含相同的一个或多个值,其中基元值类型相等性比较简单就是比较值是否相等,而一下其他类型值相等性较为复杂,因为它需要用户了解类型对值相等性的定义方式。
而在C#中有一共有四种相等性判断方法:
//Object中定义的三个方法

//双等号
public static bool operator == (Class left, Class right);
1、Object.ReferenceEquals(Object objA, Object objB)静态方法:从名称中便可知它用来比较两者是否是相同的引用,也永远不应该去重写该方法。它对于值类型对象的比较永远返回false;如果objA和objB是相同的实例或如果两者均null返回true。
2、Object.Equals(Object objA, Object objB)静态方法:该方法也永远不需要重写,因为它最终会把判断权交给参数objA的实例Equals方法。
3、Object中的实例方法Equals,因为它是虚方法,所以可以在其他类中重写它。该方法的默认实现还是比较两者是否为同一个引用,即相当于ReferenceEquals。但是微软在所有值类型的基类System.ValueType中重写了该方法,用来比较值相等。
4、比较运算符==对于预定义的值类型,如果其操作数的值相等,则相等运算符 (==) 返回 true,否则返回 false。 对于 string 外的引用类型,如果两个操作数引用同一对象,则 == 会返回 true。 对于 string 类型,== 会比较字符串的值。
其实通过微软对相等性方法的定义可以看出来,Equals主要是做值相等性判断,对于自定义的类型的话,如果想要实现判断值相等,需要自己根据实际情况重写Equals函数的。
第二、Equals
进入正题,首先说说Equals。
1、引用类型
class People
{
public string Name { get; set; } public People(string name)
{
Name = name;
}
} class Program
{
static void Main(string[] args)
{
People p1 = new People("test");
People p2 = new People("test"); Console.WriteLine($"People未重写了Equals方法,p1.Equals(p2) : {p1.Equals(p2)}"); Console.ReadLine();
}
}

对于这个结果可能有人会产生疑问,p1 和p2的内容是相同的啊,为什么比较结果却是为false呢?。原因就在于在Equals是Object中的一个虚方法,而person类中没有对她进行重写,因此此时调用的仍是父类中即Object的Equals方法,而该方法的默认实现还是比较两者是否为同一个引用,即相当于ReferenceEquals,因此返回的是false。要想让它能够比较两个变量的内容是否相同,那就应该重写Equals方法,如下:
class People
{
public string Name { get; set; } public People(string name)
{
Name = name;
} public override bool Equals(object obj)
{
if(!(obj is People))
{
throw new ArgumentException("参数错误");
} People p = obj as People; return Name == p.Name;
} public override int GetHashCode()
{
return + EqualityComparer<string>.Default.GetHashCode(Name);
}
} class Program
{
static void Main(string[] args)
{
People p1 = new People("test");
People p2 = new People("test"); Console.WriteLine($"People重写了Equals方法,p1.Equals(p2) : {p1.Equals(p2)}");
Console.ReadLine();
}
}

重写Equals方法时,记得同时重写GetHashCode方法,上面的例子GetHashCode是通过vs自动生成出来的。
2、string类型
class Program
{
static void Main(string[] args)
{
string s1 = "abc";
string s2 = "abc"; Console.WriteLine($"s1.Equals(s2) : {s1.Equals(s2)}"); Console.ReadLine();
}
}

由于string是微软封装的一个字符串类,在内部它已经对Equals方法进行了重写。重写后他比较的则是两个变量的内容是否相同,如下:

对于截图中返回结果因为我装的vs是中文版的所以反应是有点问题的应该是“如果 value 参数的值与此实例的值相同,则为 true;否则为 false。 如果 value 为 null,则此方法返回 false。”。
主要摘要中的说明value也必须是string类型才行,否则也会返回false,如下:
class Program
{
static void Main(string[] args)
{
string s1 = "";
int s2 = ; Console.WriteLine($"s1.Equals(s2) : {s1.Equals(s2)}");
Console.WriteLine($"s2 类型 : {s2.GetType().FullName}"); Console.ReadLine();
}
}

这是因为s2实际类型是Int32,只是进行了装箱操作,实际调用了string.Equals的一个重载方法,如下:

也要求参数obj必须是也 System.String 对象,否则返回false。
查看string源码可以看到为什么参数必须是string类型,如下:

3、值类型
class Program
{
static void Main(string[] args)
{
int s1 = ;
int s2 = ; Console.WriteLine($"s1.Equals(s2) : {s1.Equals(s2)}"); Console.ReadLine();
}
}

对于值类型来说比较简单就是比较值是否相同,因为基元类型都重写了Equals方法,而自定义的结构类型也因为继承自System.ValueType,而System.ValueType也重写了Equals方法从而保证值类型的Equals方法是比较值是否相等。
Equals总结
1、引用类型,比较的对象是否重写了Equals,没有重写则为引用相等性比较,如果重写了Equals则要根据重写方法判断;
2、非引用类型(包含string),比较的对象是否有拆箱装箱的操作,要识别出原始类型,然后判断两个原始类型是否是同一类型,然后对值进行比较,如果重写了Equals则要根据重写方法判断;
第三、==运算符
然后来说说运算符==。
对于预定义的值类型,如果其操作数的值相等,则相等运算符 (==) 返回 true,否则返回 false。 对于 string 外的引用类型,如果两个操作数引用同一对象,则 == 会返回 true。 对于 string 类型,== 会比较字符串的值。
用户定义的值类型可以重载 == 运算符。 用户定义的引用类型情况也相似,但是默认情况下,== 的行为如上述预定义和用户定义的引用类型所述。 如果已重载 ==,则必须同时重载 !=。 对整数类型的操作通常可用于枚举。
对于自定义的结构,如果不显示重载operator ==方法,则无法使用==。
1、基元类型
class Program
{
static void Main(string[] args)
{
int num1 = ;
int num2 = ; Console.WriteLine($"num1.Equals(num2) : {num1.Equals(num2)}");
Console.WriteLine($"num1==num2 : {num1 == num2}"); Console.ReadLine();
}
}

运行上面的示例,两个语句出的结果均为true。通过ildasm.exe工具进行反编译,查看IL代码,了解底层是如何执行的。

可以看到这样一行代码:
IL_000d: call instance bool [mscorlib]System.Int32::Equals(int32)
在这里调用的是int类型Equals(Int32)方法。
现在再来看看使用==运算符比较生成的IL指令:
IL_0029: ceq
可以看到,==运行符使用的是ceq指令,它是使用CPU寄存器来比较两个值。C#==运算符底层机制是使用ceq指令对基元类型进行比较,而不是调用Equals方法。
2、引用类型
class People
{
public string Name { get; set; } public People(string name)
{
Name = name;
}
} class Program
{
static void Main(string[] args)
{
People p1 = new People("test");
People p2 = new People("test"); Console.WriteLine($"p1.Equals(p2) : {p1.Equals(p2)}");
Console.WriteLine($"p1==p2 : {p1 == p2}"); Console.ReadLine();
}
}

IL代码如下所示:

p1.Equals(p2)代码,它是通过调用Object.Equals(Object)虚方法来比较相等,这是在意料之中的事情;现在来看==运算符生成的IL代码,与基元类型一致,使用的也是ceq指令。ceq是比较两个值。如果这两个值相等,则将整数值 1 (int32) 推送到计算堆栈上;否则,将 0 (int32) 推送到计算堆栈上。因为p1和p2是两个不同的地址引用所以值是不相同的。
3、string类型
class Program
{
static void Main(string[] args)
{
string s1 = "s1";
string s2 = string.Copy(s1); Console.WriteLine($"ReferenceEquals(s1, s2) : {ReferenceEquals(s1, s2)}");
Console.WriteLine($"s1.Equals(s2) : {s1.Equals(s2)}");
Console.WriteLine($"s1==s2 : {s1 == s2}"); Console.ReadLine();
}
}

可以看到ReferenceEquals返回false,这意味着这两个变量是不同的实例,但是==运算符和Equals方法返回的均是true。在string类型中,==运算符执行的结果与Equals执行的结果一样。
同样使用过ildasm.exe工具反编译查看生成IL代码。

通过观察可以发现ReferenceEquals使用的是ceq指令,Equals使用的是string类型Equals(string)方法,==运行符使用的是:
IL_004a: call bool [mscorlib]System.String::op_Equality(string,string)
查看string源码,原因是string类型提供了==运算符的重载,如下:

因此最终还是执行了Equals方法。
需要注意的一点是,如果想重载一个类型的==运行符的实现,那么还需要重载!=操作符的实现,否则编译会报错。
4、自定义值类型
struct People
{
public string Name { get; set; } public People(string name)
{
Name = name;
} public override string ToString()
{
return Name;
}
} class Program
{
static void Main(string[] args)
{
People p1 = new People("People");
People p2 = new People("People"); Console.WriteLine($"p1.Equals(p2) : {p1.Equals(p2)}");
Console.WriteLine($"p1==p2 : {p1 == p2}"); Console.ReadLine();
}
}

根据错误提示,需要实现People结构体的==运算符重载,重载的语句如下(忽略具体的逻辑):
struct People
{
public string Name { get; set; } public People(string name)
{
Name = name;
} public override string ToString()
{
return Name;
} public static bool operator ==(People p1, People p2)
{
return p1.Name == p2.Name;
} public static bool operator !=(People p1, People p2)
{
return p1.Name != p2.Name;
}
} class Program
{
static void Main(string[] args)
{
People p1 = new People("People");
People p2 = new People("People"); Console.WriteLine($"p1.Equals(p2) : {p1.Equals(p2)}");
Console.WriteLine($"p1==p2 : {p1 == p2}"); Console.ReadLine();
}
}


通过ildasm.exe工具反编译查看IL代码,发现值类型==运算符调用也是op_Equality方法。
关于值类型,还需要说明一个问题,在不重写Equals(object)方法时,该方法实现的原理是通过反射遍历所有字段并检查每个字段的相等性;对于值类型,最好重写该方法。
5、泛型
class Program
{
static void Main(string[] args)
{
string s1 = "s1";
string s2 = string.Copy(s1); Console.WriteLine($"s1.Equals(s2) : {s1.Equals(s2)}");
Console.WriteLine($"s1==s2 : {s1 == s2}"); Console.WriteLine("----------------------------------------");
object obj1 = "obj1";
object obj2 = string.Copy((string)obj1); Console.WriteLine($"obj1.Equals(obj2) : {obj1.Equals(obj2)}");
Console.WriteLine($"obj1==obj2 : {obj1 == obj2}"); Console.ReadLine();
}
}


通过观察发现第一个==调用了string类型的静态的op_Equality方法,所以结果为True,而第二个==使用的是ceq指令,这两个实例不是同一个对象的引用,所以ceq指令执行后的结果是False。
==运算符实际上是一个静态的方法,对一非虚方法,在编译时就已经决定用调用的是哪一个方法。
==运算符总结
1、对于基元类型==运算符的底层机制使用的是ceq指令,通过CPU寄存器进行比较;
2、对于引用类型==运算符,它也使用的ceq指令来比较内存地址;
3、对于重载==运算符的类型,实际上调用的是op_equality这个特殊的方法;
4、对于值类型,Equals方法默认是通过反射遍历所有字段并检查每个字段的相等性,为了提高性能,我们需要重写该方法;
5、值类型默认情况下不能使用==运算符,需要实现==运算符的重载;
注意
1、匿名类型的两个实例的所有属性都相等时,这两个实例才相等。
2、浮点值的值相等性,由于二进制计算机上的浮点算法不精确,因此浮点值(double 和 float)的相等比较会出现问题。
NET-知识点:C#中Equals和==比较的更多相关文章
- Java基础之String中equals,声明方式,等大总结
无论你是一个编程新手还是老手,提到String你肯定感觉特别熟悉,因为String类我们在学习java基础的时候就已经学过,但是String类型有我们想象的那么简单吗?其实不然,String类型的知识 ...
- Java中equals与==和comparaTo的区别
一.先说说Java中equals和==的区别: Java中的数据类型,可分为两类: 1.基本数据类型(也叫原始数据类型) 八大基本数据类型 char byte short int long doubl ...
- java中equals和==的区别 (转)
java中equals和==的区别 值类型是存储在内存中的堆栈(以后简称栈),而引用类型的变量在栈中仅仅是存储引用类型变量的地址,而其本身则存储在堆中. ==操作比较的是两个变量的值是否相等,对于引 ...
- 【转】Java中equals和==的区别
[转]Java中equals和==的区别 java中的数据类型,可分为两类: 1.基本数据类型,也称原始数据类型.byte,short,char,int,long,float,double,boole ...
- C#中 Equals和= =的区别
C#中 Equals和= =的区别 前言:最近感觉技术进步实在是太慢,一直被游戏缠身不能自拔哈哈,但是游戏打多了真的是感觉整个人浮躁的不行,所以我现在要去游戏多写代码多看书,今天在博客园中看到一个前辈 ...
- (转)Java中equals和==的区别
java中的数据类型,可分为两类: 1.基本数据类型,也称原始数据类型.byte,short,char,int,long,float,double,boolean 他们之间的比较,应用双等号( ...
- Java:验证在类继承过程中equals()、 hashcode()、toString()方法的使用
以下通过实际例子对类创建过程汇中常用的equals().hashcode().toString()方法进行展示,三个方法的创建过程具有通用性,在项目中可直接改写. //通过超类Employee和其子类 ...
- java中equals相同,hashcode一定相同ma
一.jdk中equals和hashcode的定义和源码进行分析 1.java.lang.Object中对equals()方法的定义 java.lang.Object中对hashCode()方法的定义 ...
- Java 中 Equals和==的区别(转)
另外一篇参考: https://blog.csdn.net/striverli/article/details/52997927 在谈论equals和==的区别前,我们先简单介绍一下JVM中内存分配的 ...
- java中equals()和==的区别
java中的数据类型 基础数据类型 基础数据类型有byte.short.char.int.long.float.double.bool.String.除了 String 会比较地址,其它的基础类型的比 ...
随机推荐
- 洛谷P4831 Scarlet loves WenHuaKe
这道题告诉我们推式子的时候头要够铁. 题意 问一个\(n\times m\)的棋盘,摆上\(n\times 2\)个中国象棋的炮使其两两不能攻击的方案数,对\(998244353\)取模. \((n\ ...
- WebSite下创建webapi
注意这里说的是WebSite,不是Webapp 就是我们常说的新建网站,而不是新建项目 直接上代码: 1 在要在website下创建,那么应该这么干.先添加引用和global.asax 2 然后创建对 ...
- 【刷题】BZOJ 2151 种树
Description A城市有一个巨大的圆形广场,为了绿化环境和净化空气,市政府决定沿圆形广场外圈种一圈树.园林部门得到指令后,初步规划出n个种树的位置,顺时针编号1到n.并且每个位置都有一个美观度 ...
- HGOI20180814 (NOIP 模拟Day1)
100pts=40+60+0 rank 56 若串联那么显然是这样: 若并联那么显然是这样: 串联时C<1,并联时C>1,贪心策略<1时尽可能串联,>1时尽可能并联 考虑这样一 ...
- 【转】ubuntu 12.04下如何开启 NFS 服务 & 设置
在嵌入式Linux开发中,利用NFS服务从开发板访问Linux主机是个高效&方便的调试方法,在程序调试过程中可以避免多次下载程序到开发板.但这需要在Linux主机上首先开通NFS服务. 以ub ...
- 解题:SDOI 2014 重建
题面 做这个这个题需要稍微深入理解一点矩阵树定理:套矩阵树定理得到的东西是有意义的,它是“所有生成树边权乘积之和”(因为度数矩阵是点的边权和,邻接矩阵是边权),即$\sum_{t}\prod_{e∈t ...
- javamail插件发送不同类型邮件方式
一.RFC882文档简单说明 RFC882文档规定了如何编写一封简单的邮件(纯文本邮件),一封简单的邮件包含邮件头和邮件体两个部分,邮件头和邮件体之间使用空行分隔. 邮件头包含的内容有: from字段 ...
- 在angular中利用分页插件进行分页
必需:angular分页js和css 当然还有angular.js 还需要bootstrap的css angular.min.js (下面我直接把插件粘贴上去了,以免有的同学还要去找.是不是很贴 ...
- 2018年5月6日GDCPC (广东赛区)总结
试机是队友浩哥一个人去的,因为觉得华工去了不少次了,环境也比较熟悉了.直到看到了现场环境,感觉有些拥挤,不如从前那样宽敞,增加了一些紧张的不适感. 比赛开始时,我们三人分头读题,虽说题目比较简短,但第 ...
- Codeforces 338 D. GCD Table
http://codeforces.com/problemset/problem/338/D 题意: 有一张n*m的表格,其中第i行第j列的数为gcd(i,j) 给出k个数 问在这张表格中是否 有某一 ...