C# ReferenceEquals(), static Equals(), instance Equals(), 和运算行符==之间的关系
C#充许你同时创建值类型和引用类型。两个引用类型的变量在引用同一个对象时,它们是相等的,就像引用到对象的ID一样。两个值类型的变量在它们的类型和内容都是相同时,它们应该是相等的。这就是为什么相等测试要这么多方法了。
先从两个你可能从来不会修改的方法开始。
ReferenceEquals():
Object.ReferenceEquals()在两个变量引用到同一个对象时返回true,也就是两个变量具有相同的对象ID。不管比较的类型是引用类型还是值类型的,这个方法总是检测对象ID,而不是对象内容。是的,这就是说当你测试两个值类型是否相等时,ReferenceEquals()总会返回false,即使你是比较同一个值类型对象,它也会返回false。这里有两个装箱,因为参数要求两个引用对象,所以用两个值类型来调用该方法,会先使两个参数都装箱,这样一来,两个引用 对象自然就不相等了。
int i = ;
int j = ;
if ( Object.ReferenceEquals( i, j ))
Console.WriteLine( "Never happens." );
else
Console.WriteLine( "Always happens." );
if ( Object.ReferenceEquals( i, i ))
Console.WriteLine( "Never happens." );
else
Console.WriteLine( "Always happens." );
都会输出:Never happens.
Object.Equals():
静态的Object.Equals()方法是像下面这样实现的:
public static bool Equals( object left, object right )
{
// Check object identity
if (left == right )
return true;
// both null references handled above
if ((left == null) || (right == null))
return false;
return left.Equals (right);
}
也就是说,静态的Equals()是使用左边参数实例的Equals()方法来断定两个对象是否相等。
与ReferenceEquals()一样,你或许从来不会重新定义静态的Object.Equals()方法,因为它已经确实的完成了它应该完成的事:在你不知道两个对象的确切类型时断定它们是否是一样的。因为静态的Equals()方法把比较委托给左边参数实例的Equals(),它就是用这一原则来处理另一个类型的。
Object.Equals()方法使用对象的ID来断定两个变量是否相等。这个默认的Object.Equals()函数的行为与Object.ReferenceEquals()确实是一样的。
现在你应该明白为什么你从来不必重新定义静态的ReferenceEquals()以及静态的Equals()方法了吧。
但是请注意,值类型是不一样的。System.ValueType并没有重载Object.Equals(),记住,System.ValueType是所有你所创建的值类型(使用关键字struct创建)的基类。两个值类型的变量相等,如果它们的类型和内容都是一样的。
ValueType.Equals()是所有值类型的基类。为了提供正确的行为,它必须比较派生类的所有成员变量,而且是在不知道派生类的类型的情况下。在C#里,这就意味着要使用反射。对反射而言它们有太多的不利之处,特别是在以性能为目标的时候。
Object.Equals():
现在来讨论你须要重载的方法。但首先,让我们先来讨论一下这样的一个与相等相关的数学性质。你必须确保你重新定义的方法的实现要与其它程序员所期望的实现是一致的。这就是说你必须确保这样的一个数学相等性质:相等的自反性,对称性和传递性。自反性就是说一个对象是等于它自己的,不管对于什么类型,a==a总应该返回true;对称就是说,如果有a==b为真,那么b==a也必须为真;传递性就是说,如果a==b为真,且b==c也为真,那么a==c也必须为真,这就是传递性。
现在是时候来讨论实例的Object.Equals()函数了,包括你应该在什么时候来重载它。
在大多数情况下,你可以为你的任何值类型重载一个快得多的Equals()。简单的推荐一下:在你创建一个值类型时,总是重载ValueType.Equals()。
现在你知道什么时候应该重载你自己的Object.Equals(),你应该明白怎样来实现它。值类型的比较关系有很多装箱的实现。对于用户类型,你的实例方法须要遵从原先定义行为(译注:前面的数学相等性质),从而避免你的用户在使用你的类时发生一些意想不到的行为。这有一个标准的模式:
public class Foo
{
public override bool Equals( object right )
{
// check null:
// the this pointer is never null in C# methods.
if (right == null)
return false;
if (object.ReferenceEquals( this, right ))
return true;
// Discussed below.
if (this.GetType() != right.GetType())
return false;
// Compare this type's contents here:
return CompareFooMembers(
this, right as Foo );
}
}
首先,Equals()决不应该抛出异常,这感觉不大好。两个变量要么相等,要么不等;没有其它失败的余地。直接为所有的失败返回false,例如null引用或者错误参数。
第一个检测断定右边的对象是否为null,这样的引用上没有方法检测,在C#里,这决不可能为null。在你调用任何一个引用到null的实例的方法之前,CLR可能抛出异常。下一步的检测来断定两个对象的引用是否是一样的,检测对象ID就行了。这是一个高效的检测,并且相等的对象ID来保证相同的内容。这个步骤是很重要的,首先,应该注意到它并不一定是Foo类型,它调用了this.GetType(),这个实际的类型可能是从Foo类派生的。其次,这里的代码在比较前检测了对象的确切类型。这并不能充分保证你可以把右边的参数转化成当前的类型。这个测试会产生两个细微的BUG。考虑下面这个简单继承层次关系的例子:
public class B
{
public override bool Equals( object right )
{
// check null:
if (right == null)
return false; // Check reference equality:
if (object.ReferenceEquals( this, right ))
return true; // Problems here, discussed below.
B rightAsB = right as B;
if (rightAsB == null)
return false; return CompareBMembers( this, rightAsB );
}
} public class D : B
{
// etc.
public override bool Equals( object right )
{
// check null:
if (right == null)
return false; if (object.ReferenceEquals( this, right ))
return true; // Problems here.
D rightAsD = right as D;
if (rightAsD == null)
return false; if (base.Equals( rightAsD ) == false)
return false; return CompareDMembers( this, rightAsD );
} } //Test:
B baseObject = new B();
D derivedObject = new D(); // Comparison 1.
if (baseObject.Equals(derivedObject))
Console.WriteLine( "Equals" );
else
Console.WriteLine( "Not Equal" ); // Comparison 2.
if (derivedObject.Equals(baseObject))
Console.WriteLine( "Equals" );
else
Console.WriteLine( "Not Equals" );
返回Equals和Not Equal
在任何可能的情况下,你都希望要么看到两个Equals或者两个Not Equals。因为一些错误,这并不是先前代码的情形。这里的第二个比较决不会返回true。这里的基类,类型B,决不可能转化为D。然而,第一个比较可能返回true。派生类,类型D,可以隐式的转化为类型B。如果右边参数以B类型展示的成员与左边参数以B类型展示的成员是同等的,B.Equals()就认为两个对象是相等的。你将破坏相等的对称性。这一架构被破坏是因为自动实现了在继承关系中隐式的上下转化。
当你这样写时,类型D被隐式的转化为B类型:
baseObject.Equals( derived )
如果baseObject.Equals()在它自己所定义的成员相等时,就断定两个对象是相等的。另一方面,当你这样写时,类型B不能转化为D类型,
derivedObject.Equals( base )
B对象不能转化为D对象,derivedObject.Equals()方法总是返回false。如果你不确切的检测对象的类型,你可能一不小心就陷入这样的窘境,比较对象的顺序成为一个问题。
当你重载Equals()时,这里还有另外一个可行的方法。你应该调用基类的System.Object或者System.ValueType的比较方法,除非基类没有实现它。前面的代码提供了一个示例。类型D调用基类,类型B,定义的Equals()方法,然而,类B没有调用baseObject.Equals()。它调用了Systme.Object里定义的那个版本,就是当两个参数引用到同一个对象时它返回true。这并不是你想要的,或者你是还没有在第一个类里的写你自己的方法。
原则是不管什么时候,在创建一个值类型时重载Equals()方法,并且你不想让引用类型遵从默认引用类型的语义时也重载Equals(),就像System.Object定义的那样。当你写你自己的Equals()时,遵从要点里实现的内容。重载Equals()就意味着你应该重写GetHashCode()。
==:
操作符==(),任何时候你创建一个值类型,重新定义操作符==()。原因和实例的Equals()是完全一样的。默认的版本使用的是引用的比较来比较两个值类型。效率远不及你自己任意实现的一个,所以,你自己写。当你比较两个值类型时,遵从原则来避免装箱。
C#给了你4种方法来检测相等性,但你只须要考虑为其中两个提供你自己的方法。你决不应该重载静态的Object.ReferenceEquals()和静态的Object.Equals(),因为它们提供了正确的检测,忽略运行时类型。你应该为了更好的性能而总是为值类型实例提供重载的Equals()方法和操作符==()。当你希望引用类型的相等与对象ID的相等不同时,你应该重载引用类型实例的Equals()。
C# ReferenceEquals(), static Equals(), instance Equals(), 和运算行符==之间的关系的更多相关文章
- ReferenceEquals()、static Equals() 、instance Equals() 与 operator==之间的联系与区别
当你创建一个用户自定义类型(类或结构)时,你需要给你的类型定义相等操作.C#中提供了几个不同的函数来验证两个对象是否满足“相等”的含义.public static bool ReferenceEqua ...
- 说说hashCode() 和 equals() 之间的关系?
上一篇关于介绍Object类下的几种方法时面试题时,提到equals()和hashCode()方法可能引出关于“hashCode() 和 equals() 之间的关系?”的面试题,本篇来解析一下这道基 ...
- Static and Instance Methods in JavaScript
class.method/instance method https://abdulapopoola.com/2013/03/30/static-and-instance-methods-in-jav ...
- C++的替代运算标记符
标记符and, and_eq, bitand, bitor, compl, not, not_eq, or, or_eq, xor, xor_eq, <%, %>, <: 和 :&g ...
- equals()和hashCode()之间的关系
在Java的基类java.lang.Object中有两个非常重要的方法: public boolean equals(Object obj) public int hashCode() 对这两个方法的 ...
- 图解equals与hashcode方法相等/不相等的互相关系
图解:比如equals相等的箭头指向hashcode相等,表示equals相等那么必有hashcode相等.而有两个箭头指向别人的表示可能是其中之一,比如hashcode相等,那么有可能equals相 ...
- Java概念辨析:equals和== equals和hashCode
1. equals和== ======================================================================================= ...
- == 和 equals,equals 与 hashcode,HashSet 和 HashMap,HashMap 和 Hashtable
一:== 和 equals == 比较引用的地址equals 比较引用的内容 (Object 类本身除外) String obj1 = new String("xyz"); Str ...
- java里equals和hashCode之间什么关系
如果要比较实际内存中的内容,那就要用equals方法,但是!!! 如果是你自己定义的一个类,比较自定义类用equals和==是一样的,都是比较句柄地址,因为自定义的类是继承于object,而objec ...
随机推荐
- boost库使用:vs2013下boost::container::vector编译出错解决
boost版本:boost_1_55_0 bug报告地址 https://svn.boost.org/trac/boost/ticket/9332 出错信息 has_member_function_c ...
- myeclipse中控制台日志比实际晚8小时解决方法及java日志处理
今天终于忍不住要解决myeclipse控制台中日志显示比实际晚8小时的问题,开始以为myeclipse编辑器时间问题,后来想想不对,myeclipse控制台打印的是tomcat的日志,随后以为是log ...
- Net-Snmp安装配置
1. 下载安装 net-snmp安装程序:net-snmp-5.4.2.1-1.win32.exe Perl安装程序:ActivePerl-5.10.0.1004-MSWin32-x86-287188 ...
- 学习php常用算法
<?php /*学用php算法*/ /*1.冒泡法 *思路分析:在要排序的一组数中,对当前还未排好的序列, *从前往后对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒. *即,每 ...
- Python 异步IO、IO多路复用
事件驱动模型 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UT ...
- Hibernate(二)——POJO对象的操作
POJO对象其实就是我们的实体,这篇博客总结一下框架对POJO对象对应数据库主键的生成策略,和一些对POJO对象的简单增删改查的操作. 一,Hibernate框架中主键的生成策略有三种方式: 1,数 ...
- Block内的强引用
众所周知,当某个对象持有着一个Block的时候,如果在Block内部使用强引用反过来持有这个对象,就会导致引用循环.为了避免引用循环,可以使用__weak修饰符,苹果的官方文档在用代码演示__weak ...
- Apple Swfit UI控件实现
不下载你会懊悔的~~ 下载地址:https://github.com/HunkSmile/Swift.git // UILabel var label = UILabel(frame: self.vi ...
- C++模板 静态成员 定义(实例化)
问一个问题: 考虑一个模板: template <typename T> class Test{ public: static std::string info; }; 对于下面若干种定义 ...
- 3. QT窗体间值的传递(续)
一.前言 上篇博客中通过重载子窗体的构造函数将主窗体的值传入到子窗体,但是在子窗体运行过程中如何才能将值动态的传入到子窗体?可以有两种办法,1.信号和槽的方式传值:2.主窗体中将传出值设置为publi ...