欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#相等性 - 三个方法和一个接口

程序员文章站 2022-06-28 20:42:30
简介 C#(.NET)的object类里面有三个关于判断相等性的方法: public virtual bool Equals(object obj) public static bool Equals(object objA, object objB) public static bool Refe ......

简介

c#(.net)的object类里面有三个关于判断相等性的方法:

  • public virtual bool equals(object obj)
  • public static bool equals(object obja, object objb)
  • public static bool referenceequals(object obja, object objb)

还有一个接口:iequatable<t>也可以用来判断相等性。

 

virtual bool equals()

比较自定义class

C#相等性 - 三个方法和一个接口

比较这个class的两个实例,它们的属性值是一样的:

 C#相等性 - 三个方法和一个接口

输出结果:

C#相等性 - 三个方法和一个接口

之所以结果是false,是因为object.equals()评估的是引用的相等性,除非进行了重写

 

比较string

这是两个字符串,而且使用string.copy()可以保证它们不指向同一个地址(如果不使用string.copy(),而直接赋两个同样的值,那么可能会发生字符串驻留问题:):

C#相等性 - 三个方法和一个接口

这时输出的结果是:

C#相等性 - 三个方法和一个接口

 

但是我们看一下string这个类,可以发现string有很多equals()方法:

C#相等性 - 三个方法和一个接口

 

如果按照上面这么写的话,它并没有调用object.equals()方法。所以我们改一下代码:

C#相等性 - 三个方法和一个接口

这时调用的是object.equals()方法,它的输出依然是:

C#相等性 - 三个方法和一个接口

 

这是因为string类对object的equals()方法进行了重写,重写后比较的是字符串的值

除了string之外,delegates和tuples也对object.equals()方法进行了重写。不过对大部分的.net类型来说,object.equals()比较的是引用。

 

比较值类型

值类型是存放在stack上面的,它们通常没有引用,除非你对它们进行装箱操作。

那么对值类型使用object.equals()方法,应该没有什么意义。。。

 

有这么一个自定义的struct:

C#相等性 - 三个方法和一个接口

 

然后进行两组比较:

C#相等性 - 三个方法和一个接口

输出结果是:

C#相等性 - 三个方法和一个接口

很显然,结果有点出乎我的意料,针对这个struct类型,object.equals()比较的是它们的值。

这是因为所有的struct都继承于system.valuetype,而system.valuetype继承于system.object,system.valuetype它对object.equals()方法进行了重写,重写的方法里会比较值类型里面所有的字段(field),如果所有字段都相等,那么就返回true

但是system.valuetype的重写是使用反射来找到所有的字段(fields),所以性能比较差。

所以针对值类型最好的办法是自己重写一下equals()方法。

 

总结

默认情况下,针对引用类型,object.equals()比较的是引用;针对值类型,object.equals()比较的是值。

但是所有的类型都可以重写object.equals()方法,例如string。

 

静态的 equals() 方法

比较null

使用object virtual的equals()方法可以应付大部分情况,但是如果该引用是null,那么使用该方法就会报错了:

C#相等性 - 三个方法和一个接口

这时候我们就可以使用object类的静态equals()方法:

C#相等性 - 三个方法和一个接口

(也可以不写object)

而结果当然是:

C#相等性 - 三个方法和一个接口

 

比较两个null

C#相等性 - 三个方法和一个接口

结果是:

C#相等性 - 三个方法和一个接口

在.net/.net core 里面,null和null是相等的。

 

源码

C#相等性 - 三个方法和一个接口

静态equals()方法的源码其实很简单,除了检查null之外,它会给出和virtual equals()方法同样的结果。

如果你对virtual的equals()方法进行了重写,而由于静态的equals()方法就会调用重写的virtual equals()方法,所以这两个方法要保持一贯性。

 

静态 reference equals() 方法

它和前两种方法有点像,但是也不尽相同。

虽然virtual和静态的equals()方法通常会比较引用,但是virutal的方法可以被重写,从而比较的是值,例如string。所以使用referenceequals()来比较两个变量是否指向同一个实例是更安全准确的

看下面这两个比较:

C#相等性 - 三个方法和一个接口

第一个比较调用的是object的virtual equals()方法,但是string对其进行了重写,比较的是值:

C#相等性 - 三个方法和一个接口

而第二个比较是object的静态的referenceequals()方法,由于是静态的,所以没法重写:

C#相等性 - 三个方法和一个接口

而c#里的==是什么原理,以后再说。

 

iequatable<t>

system.object的static bool equals(object obj)这个方法,因为其参数是object类型,所以它可以对任何引用类型进行比较。但是如果想比较值类型的话,那么值类型就会被装箱,然后再进行比较。但是装箱的动作会有性能损耗,而之所以采用值类型的主要原因就是因为性能。所以这是一个问题。

再者,使用该方法来比较两个不相干的类型,比如apple和book这两个class,比较的时候不会报错,但是这没有任何意义。这就是因为参数不是强类型,才会出现这些问题。

而iequatable<t>这个接口就可以解决这些问题。

它只定义了一个方法:bool equals(t other)。

 

例子,三个int:

C#相等性 - 三个方法和一个接口

使用它的equals()方法:

C#相等性 - 三个方法和一个接口

可以看到除了object.equals(object obj)这个方法外,它还有一个equals(int obj)这个方法,它的参数是强类型的,这是因为int实现了iequatable<t>接口。

而其源码大致如下:

C#相等性 - 三个方法和一个接口

所以平时比较int的时候使用==即可。

 

所有的原始类型都实现了iequatable<t>接口。int, byte...

而iequatable<t>对值类型非常有用。

但是对引用类型没有太大的用处,因为引用类型比较时不存在装箱问题,而且iequatable<t>在继承方面还是存在问题的,但是string还是实现了iequatable<t>接口,因为string是seal的,不存在继承。

 

需要注意的是如果实现了iequatable<t>,那么它的实现方法和重写的object.equals()方法应该保持一致,做同样的事。