理解C#语言中相等Equality 和唯一 Identity

c#有一个“Equals”方法,可以用来比较两个对象。我将试着用例子来解释等式和同一性的概念。

namespace TestEqualityDemo  
{  
 class Program  
    {  
 static void Main(string[] args)  
        {  
            TestEquality test1 = new TestEquality();  
            test1.FirstName = "Tom";  
            test1.FirstName = "Cruise";  
 
            TestEquality test2 = new TestEquality();  
            test2.FirstName = "Tom";  
            test2.FirstName = "Cruise";  
            TestEquality test3 = test2;  
 bool areEqual = test1.Equals(test2);  
            System.Console.WriteLine("Are test1 and test2 are Equal:" + areEqual);  
 
            areEqual = test2.Equals(test3);  
            System.Console.WriteLine("Are test2 and test3 are Equal:" + areEqual);  
            System.Console.ReadLine();  
        }  
    }  
 
 class TestEquality  
    {  
 public string FirstName { get; set; }  
 public string LastName { get; set; }  
    }  
}  

输出:

  1. Are test1 and test2 Equal:False
  2. Are test2 and test3 Equal:True

在上面的例子中

即使test1和test2包含FirstName和LastName的值相同,“Equals”方法也返回False。这是因为Equals方法的默认实现不检查是否相等;它检查Identity(对象引用地址)。这意味着test1和test2必须引用完全相同的对象,然后只有它返回True,否则,它将返回False。

当test2和test3引用相同的对象时,它返回True。根据上面的程序,我们可以得出等号的默认实现,这意味着只有当两个变量指向同一个对象时,它才会返回True。

然后,出现了如何在c#中检查等式的问题,答案是覆盖Equals方法的默认实现。

这里是Equals方法的默认实现。

public virtual bool Equals(Object obj)  
{  
 //If both the references points to the same object then only return true 
 if (this == obj)  
    {  
 return true;  
    }  
 return false;  
} 

必须考虑以下几点来覆盖Equals方法,

如果obj参数为空,则返回false

如果this和obj引用相同的对象,则返回True。这可以在与许多字段进行比较时提高性能

如果this和obj指的是不同的类型,则返回False,因为没有必要比较不同类型的对象;例如,如果我们比较一个字符串对象和DateTime对象,因为它们在任何情况下都不相等。

将该对象的每个字段与obj对象的相应字段进行比较。

最后,调用基类=方法。

以上是重写Equals方法进行比较的最佳步骤。

让我们为TestEquality类实现Equals方法:

namespace TestEqualityDemo  
{  
 class Program  
    {  
 static void Main(string[] args)  
        {  
            TestEquality test1 = new TestEquality();  
            test1.FirstName = "Tom";  
            test1.FirstName = "Cruise";  
 
            TestEquality test2 = new TestEquality();  
            test2.FirstName = "Tom";  
            test2.FirstName = "Cruise";  
 
            TestEquality test4 = new TestEquality();  
            test2.FirstName = "Will";  
            test2.FirstName = "Smith";  
 
            TestEquality test3 = test2;  
 bool areEqual = test1.Equals(test2);  
            System.Console.WriteLine("Are test1 and test2 Equal:" + areEqual);  
 
            areEqual = test2.Equals(test3);  
            System.Console.WriteLine("Are test2 and test3 Equal:" + areEqual);  
            System.Console.ReadLine();  
 
            areEqual = test2.Equals(test4);  
            System.Console.WriteLine("Are test2 and test4 Equal:" + areEqual);  
            System.Console.ReadLine();  
        }  
    }  
 
 class TestEquality  
    {  
 public string FirstName { get; set; }  
 public string LastName { get; set; }  
 
 public override bool Equals(System.Object obj)  
        {  
 //If the obj argument is null return false 
 if (obj == null)  
 return false;  
 //If both the references points to the same object then only return true 
 if (this == obj)  
 return true;  
 
 //If this and obj are referring to different type return false 
 if (this.GetType() != obj.GetType())  
 return false;  
 
 //Compare each field of this object with respective field of obj object 
            TestEquality test = (TestEquality)obj;  
 if (this.FirstName == test.FirstName &&  
 this.LastName == test.LastName)  
            {  
 return true;  
            }  
 return false;  
        }  
    }  
}  

输出:

  1. Are test1 and test2 Equal:True
  2. Are test2 and test3 Equal:True
  3. Are test2 and test4 Equal:False

在上面的程序中,

test1和test2的所有字段都相等,所以Equals方法返回true。

test2和test3引用同一个对象,因此它也会返回false。

在test2和test4中,如果FirstName和LastName的值不同,则返回false

重写的Equals方法必须遵循下面的规则:

=必须是自反的,也就是x.Equals(x)必须返回true。

=必须是对称的,即 x.Equals(y)必须返回与 y.Equals(x)相同的值

等号必须是传递i。如果x.Equals(y) 返回true, y.Equals(z) 返回true,那么x.Equals(z)必须返回true。

如果重写的Equals方法不遵循上述规则,那么您的应用程序可能会中断或产生意外结果。

另外,在C#里为什么重载了Equals()就要重载GetHashCode()?发现如果只重载Equals的话,编译器会给一个警告: warning CS0659: 'UseCom.Program.b' overrides Object.Equals(object o) but does not override Object.GetHashCode()

在解释这个问题之前需要先把Equals()和GetHashCode()方法进行深入了解。

首先先谈一下Equals()这个方法:

Equals()方法,来自于Object,是我们经常需要重写的方法。此方法的默认实现大概是这样的:

public virtual bool Equals(object obj)
{
  if(obj==null) return false;
  if(GetType() != obj.GetType()) return false;
  return true;
}

在很多地方都是特殊处理的,此处就不做深究了。

Ps:按Jeffrey Richter的说法,在值类型使用Equals()时,因为Equals()使用了反射,在比较时会影响效率。

说完Equals()后再来聊一聊GetHashCode()。

其实GetHashCode()在操作值类型的时候也是被System.ValueType()重写的。经过楼主测试的几个常用值类型来看,值类型的GetHashCode()基本都是原值输出(特指整数,Int32除外),真实性有待验证。

结果如下:

说完值类型,说一下引用类型,先看下面这张运行结果:

从上图的结果可以看出,虽然string是引用类型,但是只要值一样,返回的HashCode也是一样的,这取决于它的特殊性。而我们自己写的类型Coordinates同样的值但返回的HashCode却不一样,我们可以简单的理解为是coor1与coor2的内存地址不同,所以CLR认为它们是不一样的。

Ps:在程序的生命周期中,相同的对象、变量返回的HashCode是相同的,并且是唯一的。但是绝对不允许做持久性存储,程序一旦结束并重新启动后,同样的对象无法获得上次程序运行时的HashCode。

了解了两个方法后,开始今天的重点话题。

其实在上面的两个对象中(coor1、coor2),coor1.Equals(coor2)的返回结果为false(因为内存地址不同),如果我们想让它们的返回结果为true的话,只能重写Equals方法(如下图)。

重点来了,重写完Equals以后,vs发出了警告,虽然程序猿从来都是无视警告的,但这个警告确实有必要了解一下,先来看下面这三段代码。

代码段一、二:

代码段三:

看完这三段代码,应该就理解为什么要重写Equal时有必要重写GetHashCode了。

当然,如果你没打算在代码中使用Dictionary或HashTable就无所谓写不写了,换句话说,如果要把引用类型做为Dictionary或HashTable的key使用时,必须重写这两个方法。

。其结果就是,存储的时候你可能任性的存,在取值的时候就是你哭着找不着娘了。

好了,说了这么多你应该对这两个方法有了重新的认识了吧。如果还是不明白的话,用代码实现一下,保准明白。

http://www.cnblogs.com/xiaochen-vip8/articles/5506478.html

原文发布于微信公众号 - 程序你好(codinghello)

原文发表时间:2018-06-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

由for V.S. for each想到的

一直想写一系列如何提高Performance和Scalability的文章,把我的相关经验和所知道的相关的技巧同大家分享。前一阵在园子里有一篇讨论for eac...

19580
来自专栏技术博客

编写高质量代码改善C#程序的157个建议[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

  本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容:

10630
来自专栏二进制文集

JSON Java 解析

要使程序可以运行必须引入JSON-lib包——org.json.jar包。综合来看,这个JAR包比较好用。

18620
来自专栏林德熙的博客

win10 uwp unix timestamp 时间戳 转 DateTime

有时候需要把网络的 unix timestamp 转为 C# 的 DateTime ,在 UWP 可以如何转换?

7110
来自专栏菩提树下的杨过

装箱与值类型虽然很容易理解,但是在实际使用中,并不总是能100%用对

public struct Point { private int m_x, m_y; public Poi...

21660
来自专栏技术博客

一步一步学Linq to sql(一):预备知识

  Linq to sql(或者叫DLINQ)是LINQ(.NET语言集成查询)的一部分,全称基于关系数据的 .NET 语言集成查询,用于以对象形式管理关系数据...

9710
来自专栏Java后端技术

22中编程语言的HelloWorld

8310
来自专栏hbbliyong

C#基础知识回顾-- 反射(3)

获取Type对象的构造函数: 前一篇因为篇幅问题因为篇幅太短被移除首页,反射这一块还有一篇“怎样在程序集中使用反射”, 其他没有什么可以写的了,前两篇主要是铺...

30160
来自专栏技术博客

Asp.net MVC后台 XML、DataTable、DataSet之间的数据转换

  上面的方法只是将XMl字符串读入到DataSet中,然后再冲DataSet中查找先前定义过的DataTable即可。

15020
来自专栏码农阿宇

关于C#委托的一些学习笔记

1.什么是委托就是把方法作为参数传给另一个方法。委托说指向的函数,必须和函数具有相同的签名(返回值和参数类型) Public delegate void D...

27970

扫码关注云+社区

领取腾讯云代金券