引言

最近一个朋友正在找工作,他说在笔试题中遇到Equals和==有什么区别的题,当时跟他说如果是值类型的,它们没有区别,如果是引用类型的有区别,但string类型除外。为了证实自己的说法,也研究了一下,以免误导别人,这里将研究结果总结一下,如果我有什么地方说的不对的地方,望指出。

相等性

在定义类或结构时,您将决定为类型创建值相等性(或等效性)的自定义定义是否有意义。 通常,当类型的对象预期要添加到某类集合时,或者当这些对象主要用于存储一组字段或属性时,您将实现值相等性。 您可以基于类型中所有字段和属性的比较来定义值相等性,也可以基于子集进行定义。 但在任何一种情况下,类和结构中的实现均应遵循五个等效性保证条件:

  1. x.Equals(x) 返回 true. 。这称为自反属性。

  2. x.Equals(y) 返回与 Equals(x) 相同的值。 这称为对称属性。

  3. 如果 (x.Equals(y) && y.Equals(z)) 返回 true,则 x.Equals(z) 返回 true。 这称为可传递属性。

  4. 只要不修改 x 和 y 所引用的对象,x.Equals(y) 的后续调用就返回相同的值。

  5. x.Equals(null) 返回 false。 但是,null.Equals(null) 会引发异常;它不遵循上面的第二条规则。

您定义的任何结构已经具有它从 Object.Equals(Object) 方法的 System.ValueType 重写中继承的默认值相等性实现。 此实现使用反射来检查类型中的所有公共和非公共字段以及属性。 尽管此实现可生成正确的结果,但与您专门为类型编写的自定义实现相比,它的速度相对较慢。
类和结构的值相等性的实现详细信息不同。 但是,类和结构都需要相同的基础步骤来实现相等性:
重写 Object.Equals(Object)虚方法。 大多数情况下,您的 bool Equals( object obj ) 实现应只调入作为 System.IEquatable<T> 接口的实现的类型特定 Equals 方法。 (请参见步骤 2。)
通过提供类型特定的 Equals 方法实现 System.IEquatable<T> 接口。 实际的等效性比较将在此接口中执行。 例如,您可能决定通过仅比较类型中的一两个字段来定义相等性。 不要从 Equals 中引发异常。 仅适用于类:此方法应仅检查类中声明的字段。 它应调用 base.Equals 来检查基类中的字段。 (如果类型直接从 Object 中继承,则不要这样做,因为 Object.Equals(Object) 的 Object 实现会执行引用相等性检查。)
可选,但建议这样做:重载 == 和 != 运算符。
重写 Object.GetHashCode,使具有值相等性的两个对象生成相同的哈希代码。
可选:若要支持“大于”或“小于”定义,请为类型实现 IComparable<T> 接口,并同时重载 <= 和 >= 运算符。

——MSDN(http://msdn.microsoft.com/zh-cn/library/dd183755.aspx)这里将msdn的说法贴在此处,方便查看。

值类型

这里就以int类型的为代表进行分析,在分析之前先复习一下什么是重载?重载:简单的说就是一个类中的方法与另一个方法同名,但是参数列表个数或者类型或者返回值类型不同,则这两个方法构成重载。那么重载方法的调用规则是什么?那么先看下面的一段测试代码:

  1. namespace Wolfy.EqualsDemo
  2. {
  3. class Program
  4. {
  5. static void Main(string[] args)
  6. {
  7. int a =, b = ;
  8. Console.WriteLine(Add(a,b));
  9. Console.Read();
  10. }
  11. static int Add(object a, object b)
  12. {
  13. Console.WriteLine("调用了object类型参数列表的方法:");
  14. return (int)a + (int)b;
  15. }
  16. static int Add(int a, float b)
  17. {
  18. Console.WriteLine("调用了int,float类型参数列表的方法:");
  19. return a + (int)b;
  20. }
  21. static int Add(int a, int b)
  22. {
  23. Console.WriteLine("调用了int类型参数列表的方法:");
  24. return a + b;
  25. }
  26. }
  27. }

测试结果:

说明根据传入实参的类型,优先匹配最相近的形参列表的方法。

那么我们将Add(int a ,int b)这个方法注释掉,那么会调用哪个方法?

为什么花费那么多口舌说明上面的问题,那么现在看一下Int32反编译的代码:

Int32有一个自己的Equals方法,有一个重写的Equals方法,如果两个int类型的值进行比较,Equals和==是一样的,因为它优先调用了下面的Equals方法,如果是下面的代码,则会选择重写的Equals方法。

  1. static void Main(string[] args)
  2. {
  3. int a = ;
  4. object b = ;
  5. Console.WriteLine(a.Equals(b));
  6. Console.Read();
  7. }

可见,对于值类型的Equals和==是一样的。

引用类型

在类(引用类型)上,两种 Object.Equals(Object) 方法的默认实现均执行引用相等性比较,而不是值相等性检查。 当实施者重写虚方法时,目的是为了为其指定值相等性语义。
即使类不重载 == 和 != 运算符,也可以将这些运算符与类一起使用。 但是,默认行为是执行引用相等性检查。 在类中,如果您重载 Equals 方法,则应重载 == 和 != 运算符,但这并不是必需的。

——MSDN

测试代码:

  1. static void Main(string[] args)
  2. {
  3.  
  4. Person p1 = new Person() { Name = "wolfy" };
  5. Person p2 = new Person() { Name = "wolfy" };
  6. Person p3 = p2;
  7. bool r1 = p1 == p2;
  8. bool r2 = p1.Equals(p2);
  9. bool r3 = p2 == p3;
  10. bool r4 = p2.Equals(p3);
  11. bool r5 = object.ReferenceEquals(p1, p2);
  12. bool r6 = object.Equals(p1,p2);
  13. bool r7 = object.ReferenceEquals(p2, p3);
  14. Console.WriteLine("==\t"+r1);
  15. Console.WriteLine("Equals\t"+r2);
  16. Console.WriteLine("p3=p2\t"+r3);
  17. Console.WriteLine("p2.Equals(p3)\t"+r4);
  18. Console.WriteLine("object.ReferenceEquals\t" + r5);
  19. Console.WriteLine("object.Equals(p1,p2)\t" + r6);
  20. Console.WriteLine("object.ReferenceEquals(p2, p3)\t" + r7);
  21. Console.Read();
  22. }

结果:

顺便反编译一下Equals和ReferenceEquals方法,看看他们的实现如何?

  1. 1 // object
  2. 2 [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
  3. 3 public static bool Equals(object objA, object objB)
  4. 4 {
  5. 5 return objA == objB || (objA != null && objB != null && objA.Equals(objB));
  6. 6 }
  1. // object
  2. [__DynamicallyInvokable, ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
  3. public static bool ReferenceEquals(object objA, object objB)
  4. {
  5. return objA == objB;
  6. }

通过上面的代码,我们可以得出这样的结论,引用类型中Equals和ReferenceEquals的行为是相同的,==与ReferenceEquals的行为也相同,但string除外。

对特殊应用类型string的相等性,遵循值类型的相等性。string类型的反编译后的Equals方法和==代码如下:

  1. public override bool Equals(object obj)
  2. {
  3. if (this == null)
  4. {
  5. throw new NullReferenceException();
  6. }
  7. string text = obj as string;
  8. return text != null && (object.ReferenceEquals(this, obj) || (this.Length == text.Length && string.EqualsHelper(this, text)));
  9. }
  10. [__DynamicallyInvokable, ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
  11. public bool Equals(string value)
  12. {
  13. if (this == null)
  14. {
  15. throw new NullReferenceException();
  16. }
  17. return value != null && (object.ReferenceEquals(this, value) || (this.Length == value.Length && string.EqualsHelper(this, value)));
  18. }
  19. [__DynamicallyInvokable, SecuritySafeCritical]
  20. public bool Equals(string value, StringComparison comparisonType)
  21. {
  22. if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
  23. {
  24. throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
  25. }
  26. if (this == value)
  27. {
  28. return true;
  29. }
  30. if (value == null)
  31. {
  32. return false;
  33. }
  34. switch (comparisonType)
  35. {
  36. case StringComparison.CurrentCulture:
  37. return CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.None) == ;
  38. case StringComparison.CurrentCultureIgnoreCase:
  39. return CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == ;
  40. case StringComparison.InvariantCulture:
  41. return CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.None) == ;
  42. case StringComparison.InvariantCultureIgnoreCase:
  43. return CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == ;
  44. case StringComparison.Ordinal:
  45. return this.Length == value.Length && string.EqualsHelper(this, value);
  46. case StringComparison.OrdinalIgnoreCase:
  47. if (this.Length != value.Length)
  48. {
  49. return false;
  50. }
  51. if (this.IsAscii() && value.IsAscii())
  52. {
  53. return string.CompareOrdinalIgnoreCaseHelper(this, value) == ;
  54. }
  55. return TextInfo.CompareOrdinalIgnoreCase(this, value) == ;
  56. default:
  57. throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
  58. }
  59. }
  60. [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
  61. public static bool Equals(string a, string b)
  62. {
  63. return a == b || (a != null && b != null && a.Length == b.Length && string.EqualsHelper(a, b));
  64. }
  65. [__DynamicallyInvokable, SecuritySafeCritical]
  66. public static bool Equals(string a, string b, StringComparison comparisonType)
  67. {
  68. if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
  69. {
  70. throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
  71. }
  72. if (a == b)
  73. {
  74. return true;
  75. }
  76. if (a == null || b == null)
  77. {
  78. return false;
  79. }
  80. switch (comparisonType)
  81. {
  82. case StringComparison.CurrentCulture:
  83. return CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.None) == ;
  84. case StringComparison.CurrentCultureIgnoreCase:
  85. return CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == ;
  86. case StringComparison.InvariantCulture:
  87. return CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.None) == ;
  88. case StringComparison.InvariantCultureIgnoreCase:
  89. return CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == ;
  90. case StringComparison.Ordinal:
  91. return a.Length == b.Length && string.EqualsHelper(a, b);
  92. case StringComparison.OrdinalIgnoreCase:
  93. if (a.Length != b.Length)
  94. {
  95. return false;
  96. }
  97. if (a.IsAscii() && b.IsAscii())
  98. {
  99. return string.CompareOrdinalIgnoreCaseHelper(a, b) == ;
  100. }
  101. return TextInfo.CompareOrdinalIgnoreCase(a, b) == ;
  102. default:
  103. throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
  104. }
  105. }
  106. [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
  107. public static bool operator ==(string a, string b)
  108. {
  109. return string.Equals(a, b);
  110. }
  111. [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
  112. public static bool operator !=(string a, string b)
  113. {
  114. return !string.Equals(a, b);
  115. }

从上面的代码可以看出string类型的Equals和==是一样的。

  1. public static bool operator ==(string a, string b)
  2. {
  3. return string.Equals(a, b);
  4. }
  5. [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
  6. public static bool operator !=(string a, string b)
  7. {
  8. return !string.Equals(a, b);
  9. }

总结

值类型具有它从 Object.Equals(Object) 方法的 System.ValueType 重写中继承的默认值相等性实现。特殊的引用类型string类型,因为重写了Equals和==方法,所以string类型的Equals和==与值类型的相等性一样。对于其他的引用类型此时的Equals和==与引用相等ReferenceEquals的行为相同。

以上是由一个同事的问题引起,中间也查了很多资料,发现这篇文章在草稿箱中躺了很久了,今天突然看到就拿出来晒晒。中间修修改改,总尝试着用哪种方式来说明这个老生常谈的问题更好些。以上有些观点,纯属个人见解,如果你有更好的理解方式,不妨分享一下。如果对你有所帮助,不妨点一下推荐,让更多的人看到,说说自己对Equals和==的理解。

[c#基础]值类型和引用类型的Equals,==的区别的更多相关文章

  1. [No0000B9]C# 类型基础 值类型和引用类型 及其 对象复制 浅度复制vs深度复制 深入研究2

    接上[No0000B5]C# 类型基础 值类型和引用类型 及其 对象判等 深入研究1 对象复制 有的时候,创建一个对象可能会非常耗时,比如对象需要从远程数据库中获取数据来填充,又或者创建对象需要读取硬 ...

  2. [No0000B5]C# 类型基础 值类型和引用类型 及其 对象判等 深入研究1

    引言 本文之初的目的是讲述设计模式中的 Prototype(原型)模式,但是如果想较清楚地弄明白这个模式,需要了解对象克隆(Object Clone),Clone其实也就是对象复制.复制又分为了浅度复 ...

  3. C#基础--值类型和引用类型

    C#中大多数类型都是引用类型,只有个别特殊情况是值类型. 值类型: 枚举(enum) 结构(struct) 基础类型:int, short, char, bool....(string是引用类型) 引 ...

  4. C#值类型和引用类型与Equals方法

    1. C#的值类型和引用类型 C#的对象里面有两种类型,一个是引用类型,一个是值类型,值类型和引用类型的具体分类可以看下面的分类.   在C#中,不管是引用类型还是值类型,他们都隐式继承Object类 ...

  5. C#基础|值类型和引用类型以及传参问题

    为了明白什么是值类型和引用类型,先引入你两个概念.堆内存与栈内存   堆内存与栈内存   由于咱的描述能力有限,就不对其下定义了,来看看两者的作用.   共同点: 都是用来存放数据的   不同点: 堆 ...

  6. .NET基础知识(01)-值类型与引用类型

    常见面试题目: 1. 值类型和引用类型的区别? 2. 结构和类的区别? 3. delegate是引用类型还是值类型?enum.int[]和string呢? 4. 堆和栈的区别? 5. 什么情况下会在堆 ...

  7. Windows Phone 开发起步之旅之二 C#中的值类型和引用类型

    今天和大家分享下本人也说不清楚的一个C#基础知识,我说不清楚,所以我才想把它总结一下,以帮助我自己理解这个知识上的盲点,顺便也和同我一样不是很清楚的人一起学习下.  一说起来C#中的数据类型有哪些,大 ...

  8. .NET面试题解析(01)-值类型与引用类型

      系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 常见面试题目: 1. 值类型和引用类型的区别? 2. 结构和类的区别? 3. delegate是引用类型还 ...

  9. C#系列之值类型和引用类型

    前言 这几天一直在思考这章讨论什么, 在上一章讨论string的时候牵涉到引用类型,那么我们这一章讨论讨论一下,值类型和引用类型. 值类型和引用类型,它们的区别来源于传值方式.有人会认为值类型就存在栈 ...

随机推荐

  1. Windows系统镜像自动添加驱动程序

    2016年到了一家公司做网管,经常会为了装系统而烦恼,后来学习了WDS自动部署,但是在学习过程中发现启动镜像boot.wim中没有网卡驱动 导致wds报错,后来经过网上查找相关资料学会了如何向系统里添 ...

  2. java PKCS7Padding 加密Cannot find any provider supporting AES/CBC/PKCS7Padding 解决办法

    在java中用aes256进行加密,但是发现java里面不能使用PKCS7Padding,而java中自带的是PKCS5Padding填充,那解决办法是,通过BouncyCastle组件来让java里 ...

  3. xamarin.android 沉浸式状态栏

    public class SystemBarTintManager { /** * The default system bar tint color value. */ public static ...

  4. mac svn 终端操作命令

    svn 删除目录命令 svn 提交命令 svn commit -m zenggui 出来要提交的目录后,按shift + : + q 如遇到不明白的可以输入:svn help 比如想查询删除命令的使用 ...

  5. 使用jmx监控tomcat

    1.在tomcat启动过程中,开启相应的参数配置: -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -D ...

  6. (转)u3d设计模式

    Unity3d中UI开发的MVC模式 ,和游戏开发的其他模块类似,UI一般需要通过多次迭代开发,直到用户体验近似OK.另外至关重要的是, 我们想尽快加速迭代的过程.使用MVC模式来进行设计,已经被业界 ...

  7. 安装docker1.10

    1.安装 关闭 /etc/selinux/config # This file controls the state of SELinux on the system. # SELINUX= can ...

  8. 2014 Super Training #9 E Destroy --树的直径+树形DP

    原题: ZOJ 3684 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3684 题意: 给你一棵树,树的根是树的中心(到其 ...

  9. 给vs2010安装上cocos2d-x的模版

    开发环境:OS(WINDOWS 8.1 X64 企业版) cocos2d-x 2.2.1  vs2010 想给vs安装上cocos的模版,执行InstallWizardForVS2010.js,老是提 ...

  10. RDLC使用手册_RDLC报表部署

    原文:http://blog.csdn.net/lwjnumber/article/details/6590545 9.  RDLC报表部署(限于rdlc报表 windows应用程序) 1)    R ...