[c#基础]值类型和引用类型的Equals,==的区别

简介:

引言

最近一个朋友正在找工作,他说在笔试题中遇到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 =1, b = 1;
 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 = 1;
4             object b = 1;
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 // object
2 [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] 3 public static bool Equals(object objA, object objB) 4 { 5 return objA == objB || (objA != null && objB != null && objA.Equals(objB)); 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) == 0;
 38             case StringComparison.CurrentCultureIgnoreCase:
 39                 return CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0;
 40             case StringComparison.InvariantCulture:
 41                 return CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0;
 42             case StringComparison.InvariantCultureIgnoreCase:
 43                 return CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0;
 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) == 0;
 54                 }
 55                 return TextInfo.CompareOrdinalIgnoreCase(this, value) == 0;
 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) == 0;
 84             case StringComparison.CurrentCultureIgnoreCase:
 85                 return CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0;
 86             case StringComparison.InvariantCulture:
 87                 return CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0;
 88             case StringComparison.InvariantCultureIgnoreCase:
 89                 return CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0;
 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) == 0;
100                 }
101                 return TextInfo.CompareOrdinalIgnoreCase(a, b) == 0;
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和==的理解。

博客地址: http://www.cnblogs.com/wolf-sun/
博客版权: 本文以学习、研究和分享为主,欢迎转载,但必须在文章页面明显位置给出原文连接。
如果文中有不妥或者错误的地方还望高手的你指出,以免误人子弟。如果觉得本文对你有所帮助不如【推荐】一下!如果你有更好的建议,不如留言一起讨论,共同进步!
再次感谢您耐心的读完本篇文章。

转载:http://www.cnblogs.com/wolf-sun/p/3643863.html
目录
相关文章
|
2月前
|
Java 物联网 编译器
C#一分钟浅谈:.NET Core 与 .NET 5 区别
本文对比了 .NET Core 和 .NET 5,从历史背景、主要区别、常见问题及易错点等方面进行了详细分析。.NET Core 侧重跨平台支持和高性能,而 .NET 5 在此基础上统一了 .NET 生态系统,增加了更多新特性和优化。开发者可根据具体需求选择合适的版本。
66 7
|
3月前
|
网络协议 网络性能优化 C#
C# 一分钟浅谈:UDP 与 TCP 协议区别
【10月更文挑战第8天】在网络编程中,传输层协议的选择对应用程序的性能和可靠性至关重要。本文介绍了 TCP 和 UDP 两种常用协议的基础概念、区别及应用场景,并通过 C# 代码示例详细说明了如何处理常见的问题和易错点。TCP 适用于需要可靠传输和顺序保证的场景,而 UDP 适用于对延迟敏感且可以容忍一定数据丢失的实时应用。
68 1
|
2月前
|
开发框架 安全 .NET
C#面:Server.UrlEncode、HttpUtility.UrlDecode的区别
通过上述详细的解释和实例分析,相信大家对 `Server.UrlEncode` 和 `HttpUtility.UrlDecode` 的区别有了更深刻的理解,并能在实际开发中灵活运用。
75 0
|
3月前
|
C# 开发者
【捞底干货】C#中equals和==运算符的区别
【捞底干货】C#中equals和==运算符的区别
195 1
|
4月前
|
存储 Java C#
C# 中的值类型与引用类型
在 C# 编程中,值类型和引用类型的区别至关重要,直接影响内存管理、性能优化及编程模式选择。值类型直接存储数据(如 `int`、`float`),而引用类型存储数据的引用地址(如 `class`、`string`)。值类型的赋值涉及数据复制,适合小数据量;引用类型仅复制引用,适合大数据量处理但需关注垃圾回收。本文通过具体代码示例详细解析二者的定义、存储方式及性能影响,并提供实战案例分析及易错点避免方法,帮助读者更好地理解和应用。
109 2
|
4月前
|
C# 索引
C# 一分钟浅谈:接口与抽象类的区别及使用
【9月更文挑战第2天】本文详细对比了面向对象编程中接口与抽象类的概念及区别。接口定义了行为规范,强制实现类提供具体实现;抽象类则既能定义抽象方法也能提供具体实现。文章通过具体示例介绍了如何使用接口和抽象类,并探讨了其实现方式、继承限制及实例化差异。最后总结了选择接口或抽象类应基于具体设计需求。掌握这两者有助于编写高质量的面向对象程序。
158 5
|
5月前
|
C#
C#中的overload,overwrite,override的语义区别
以上概念是面向对象编程中实现多态性和继承的重要基石。理解它们之间的区别对于编写清晰、可维护的代码至关重要。
188 7
|
2月前
|
C# 开发者
C# 一分钟浅谈:Code Contracts 与契约编程
【10月更文挑战第26天】本文介绍了 C# 中的 Code Contracts,这是一个强大的工具,用于通过契约编程增强代码的健壮性和可维护性。文章从基本概念入手,详细讲解了前置条件、后置条件和对象不变量的使用方法,并通过具体代码示例进行了说明。同时,文章还探讨了常见的问题和易错点,如忘记启用静态检查、过度依赖契约和性能影响,并提供了相应的解决建议。希望读者能通过本文更好地理解和应用 Code Contracts。
48 3
|
1月前
|
存储 安全 编译器
学懂C#编程:属性(Property)的概念定义及使用详解
通过深入理解和使用C#的属性,可以编写更清晰、简洁和高效的代码,为开发高质量的应用程序奠定基础。
94 12
|
2月前
|
设计模式 C# 图形学
Unity 游戏引擎 C# 编程:一分钟浅谈
本文介绍了在 Unity 游戏开发中使用 C# 的基础知识和常见问题。从 `MonoBehavior` 类的基础用法,到变量和属性的管理,再到空引用异常、资源管理和性能优化等常见问题的解决方法。文章还探讨了单例模式、事件系统和数据持久化等高级话题,旨在帮助开发者避免常见错误,提升游戏开发效率。
87 4