第一、相等性比较

  其实这个问题的的本质就是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和==比较的更多相关文章

  1. Java基础之String中equals,声明方式,等大总结

    无论你是一个编程新手还是老手,提到String你肯定感觉特别熟悉,因为String类我们在学习java基础的时候就已经学过,但是String类型有我们想象的那么简单吗?其实不然,String类型的知识 ...

  2. Java中equals与==和comparaTo的区别

    一.先说说Java中equals和==的区别: Java中的数据类型,可分为两类: 1.基本数据类型(也叫原始数据类型) 八大基本数据类型 char byte short int long doubl ...

  3. java中equals和==的区别 (转)

    java中equals和==的区别  值类型是存储在内存中的堆栈(以后简称栈),而引用类型的变量在栈中仅仅是存储引用类型变量的地址,而其本身则存储在堆中. ==操作比较的是两个变量的值是否相等,对于引 ...

  4. 【转】Java中equals和==的区别

    [转]Java中equals和==的区别 java中的数据类型,可分为两类: 1.基本数据类型,也称原始数据类型.byte,short,char,int,long,float,double,boole ...

  5. C#中 Equals和= =的区别

    C#中 Equals和= =的区别 前言:最近感觉技术进步实在是太慢,一直被游戏缠身不能自拔哈哈,但是游戏打多了真的是感觉整个人浮躁的不行,所以我现在要去游戏多写代码多看书,今天在博客园中看到一个前辈 ...

  6. (转)Java中equals和==的区别

    java中的数据类型,可分为两类:  1.基本数据类型,也称原始数据类型.byte,short,char,int,long,float,double,boolean    他们之间的比较,应用双等号( ...

  7. Java:验证在类继承过程中equals()、 hashcode()、toString()方法的使用

    以下通过实际例子对类创建过程汇中常用的equals().hashcode().toString()方法进行展示,三个方法的创建过程具有通用性,在项目中可直接改写. //通过超类Employee和其子类 ...

  8. java中equals相同,hashcode一定相同ma

    一.jdk中equals和hashcode的定义和源码进行分析 1.java.lang.Object中对equals()方法的定义 java.lang.Object中对hashCode()方法的定义 ...

  9. Java 中 Equals和==的区别(转)

    另外一篇参考: https://blog.csdn.net/striverli/article/details/52997927 在谈论equals和==的区别前,我们先简单介绍一下JVM中内存分配的 ...

  10. java中equals()和==的区别

    java中的数据类型 基础数据类型 基础数据类型有byte.short.char.int.long.float.double.bool.String.除了 String 会比较地址,其它的基础类型的比 ...

随机推荐

  1. python自动化之鼠标移动

    ################################用GUI自动化控制键盘和鼠标############################### ''' http://pyautogui.r ...

  2. 一本通1641【例 1】矩阵 A×B

    1641: [例 1]矩阵 A×B sol:矩阵乘法模板.三个for循环 #include <bits/stdc++.h> using namespace std; typedef lon ...

  3. linux c 编程 ------ 常见函数

    fork():创建一个进程 exec():停止当前进程中程序的执行,让当前进程执行另一个程序 access():查看是否有操作文件的权限,可以用来判断一个文件是否存在 pipe():无名管道,用在父子 ...

  4. Python学习笔记 - 实现探测Web服务质量

    #!/usr/bin/python3# _*_ coding:utf-8 _*_import sys, osimport timeimport pycurl url = "https://d ...

  5. Java环境变量自动配置。嗯,就是用C#去配置JDK

    在跟学弟们聊天的过程中,发现一些人在首次接触Java时,对环境变量配置总是很生疏.可能是由于初学,对一些概念没有很深刻的理解.本着助人为乐的精神.我决定帮他们一下.写一个自动配置JDK环境变量的小工具 ...

  6. Spark记录-官网学习配置篇(二)

    ### Spark SQL Running the SET -v command will show the entire list of the SQL configuration. #scala/ ...

  7. bzoj千题计划273:bzoj4710: [Jsoi2011]分特产

    http://www.lydsy.com/JudgeOnline/problem.php?id=4710 答案=总方案数-不合法方案数 f[i][j] 前i种特产分给j个人(可能有人没有分到特产)的总 ...

  8. hdu 3022 Sum of Digits

    http://acm.hdu.edu.cn/showproblem.php?pid=3022 题意: 最多不超过10000组数据,每组数据给定两个数n,m,求一个最小的数,使得该数每一位之和等于n,每 ...

  9. PHP-PSR-[0-4]代码规范

    PHP-FIG 在说啥是PSR-[0-4]规范的之前,我觉得我们有必要说下它的发明者和规范者:PHP-FIG,它的网站是:www.php-fig.org.就是这个联盟组织发明和创造了PSR-[0-4] ...

  10. Mac安装WineHQ

    下载: (链接: https://pan.baidu.com/s/1o7NPhNk 密码: 5227) 安装: 先决条件: XQuartz>=2.7.7 系统设置允许未签名的包. 在https: ...