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 会比较地址,其它的基础类型的比 ...
随机推荐
- 【luogu3768】简单的数学题 欧拉函数(欧拉反演)+杜教筛
题目描述 给出 $n$ 和 $p$ ,求 $(\sum\limits_{i=1}^n\sum\limits_{j=1}^nij\gcd(i,j))\mod p$ . $n\le 10^{10}$ . ...
- 【大数据】SparkStreaming学习笔记
第1章 Spark Streaming概述 1.1 Spark Streaming是什么 Spark Streaming用于流式数据的处理.Spark Streaming支持的数据输入源很多,例如:K ...
- POJ 2240 Arbitrage / ZOJ 1092 Arbitrage / HDU 1217 Arbitrage / SPOJ Arbitrage(图论,环)
POJ 2240 Arbitrage / ZOJ 1092 Arbitrage / HDU 1217 Arbitrage / SPOJ Arbitrage(图论,环) Description Arbi ...
- 【洛谷P2661】信息传递 (updated)
题目大意:给定一棵 N 个节点的内向树森林,求该内向树森林的最小环的大小(按边计算). 题解:先删链,再计算环的大小,统计答案即可. 代码如下 #include <bits/stdc++.h&g ...
- 《剑指offer》— JavaScript(28)数组中出现次数超过一半的数字
数组中出现次数超过一半的数字 题目描述 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字.例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}.由于数字2在数组中出现了5次,超 ...
- Java中list如何利用遍历进行删除操作
转: Java中list如何利用遍历进行删除操作 2018年03月31日 10:23:41 Little White_007 阅读数:3874 Java三种遍历如何进行list的便利删除: 1.f ...
- hdu 4857 Little Devil I
http://acm.hdu.edu.cn/showproblem.php?pid=4897 题意:给你一棵树,边的颜色要么为白色,要么为黑色,初始每条边为白色,有三种操作 1.将u-v链上面的所有边 ...
- scale.fix.js
无意间在一个网站上看到的,本来是对另一个效果感兴趣的,结果看到这个放开来的js就读了一下. var metas = document.getElementsByTagName('meta'); var ...
- 浏览器存储:cookie
Cookie是什么:cookie是指存储在用户本地终端上的数据,同时它是与具体的web页面或者站点相关的.Cookie数据会自动在web浏览器和web服务器之间传输,也就是说HTTP请求发送时,会把保 ...
- HDU 1867 A + B for you again 字符匹配
解题报告:给你两个字符串,让你连接起来,没有前后顺序,要求是长度最短优先,其次是字典序最小.这题我用的是KMP,做两次匹配,分别把第一次跟第二次输入的字符串放前面,然后比较两次得到的字符窜的长度和字典 ...