8.2.2 本质分析

1 .Equals  静态方法 

Equals 静态方法实现了对两个对象的相等性判别,其在 System.Object 类型中实现过程可以表 示为:

        public static bool Equals(object objA, object objB)
        {
            if (objA == objB)
            {
                return true;
            }
            if ((objA != null) && (objB != null))
            {
                return objA.Equals(objB);
            }
            return false;
        }

  对以上过程,可以小结为:首先比较两个类型是否为同一实例,如果是则返回 true;否则将 进一步判断两个对象是否都为 null,如果是则返回 true;如果不是则返回 objA 对象的 Equals 虚方 法的执行结果。

  所以,Equals 静态方法的执行结果,依次取决于三个条件:

  l 是否为同一实例。

  l 是否都为 null。

  l 第一个参数的 Equals 实现。

  因此,通常情况下 Equals 静态方法的执行结果常常受到判等对象的影响,例如有下面的测试 过程:

    class Program5
    {
        public static void Main(string[] args)
        {
            MyClassA objA = new MyClassA();
            MyClassB objB = new MyClassB();
            Console.WriteLine(Equals(objA, objB));
            Console.WriteLine(Equals(objB, objA));
            Console.ReadKey();
        }
    }
    class MyClassA
    {
        public override bool Equals(object obj)
        {
            return true;
        }
    }
    class MyClassB
    {
        public override bool Equals(object obj)
        {
            return false;
        }
    }

//执行结果

True

False

  由执行结果可知,静态 Equals 的执行取决于==操作符和 Equals 虚方法这两个因素。因此,决 议静态 Equals 方法的执行,就要在自定义类型中覆写 Equals 方法和重载==操作符。

还应注意到,.NET 提供了 Equals 静态方法可以解决两个值为 null 对象的判等问题,而使用 ob jA.Equals(object objB)来判断两个 null 对象会抛出 NullReferenceException 异常

public static void Main()
{
object o = null;
o.Equals(null);
}

2 .ReferenceEquals  静态方法

ReferenceEquals 方法为静态方法,因此不能在继承类中重写该方法,所以只能使用 System.Ob ject 的实现代码,具体为:

public static bool ReferenceEquals(object objA, object objB)
{
return (objA == objB);
}

  可见,ReferenceEquals 方法用于判断两个引用是否指向同一个对象,也就是前文强调的引用 相等。因此以 ReferenceEquals 方法比较同一个类型的两个对象实例将返回 fasle,而.NET 认为 null 等于 null

  因此,ReferenceEquals 方法,只能用于比较两个引用类型,而以 ReferenceEquals 方法比较值 类型,必然伴随着装箱操作的执行,分配在不同地址的两个装箱的实例对象,肯定返回 false 结果

  从结果分析可知两次创建的 string 类型实例不仅内容相同,而且分享共同的内存空间,事实 上的确如此,这缘于 System.String 类型的字符串驻留机制,详细的讨论见 8.3 节“为什么特殊:str ing 类型解析”,在此我们必须明确 ReferenceEquals 判断引用相等的实质是不容置疑的。

3 .Equals  虚方法

  Equals 虚方法用于比较两个类型实例是否相等,也就是判断两个对象是否具有相同的“值”, 在 System.Object 中其实现代码,可以表示为:

public virtual bool Equals(object obj)
{
return InternalEquals(this, obj);
}

其中 InternalEquals 为一个静态外部引用方法,其实现的操作可以表示成:

if (this == obj)
return true;
else
return false;

  可见,默认情况下,Equals 方法和 ReferenceEquals 方法是一样的,Object 类中的 Equals 虚方 法仅仅提供了最简单的比较策略:如果两个引用指向同一个对象,则返回 true;否则将返回 false, 也就是判断是否引用相等。然而这种方法并未达到 Equals 比较两个对象值相等的目标,因此 Syste m.Object 将这个任务交给其派生类型去重新实现,可以说 Equals 的比较结果取决于类的创建者是 如何实现的,而非统一性约定。

  事实上,.NET 框架类库中有很多的引用类型实现了 Equals 方法用于比较值相等,例如比较两 个 System.String 类型对象是否相等,肯定关注其内容是否相等,判断的是值相等语义:

4 .== 操作 符

  在.NET 中,默认情况下,操作符“==”在值类型情况下表示是否值相等,由值类型的根类 Syste m.ValueType 提供了实现;而在引用类型情况下表示是否引用相等,而“!=”操作符与“==”语义类似。 当然也有例外,System.String 类型则以“==”来处理值相等。因此,对于自定义值类型,如果重载 E quals 方法,则应该保持和“==”在语义上的一致,以返回值相等结果;而对于引用类型,如果以覆 写来处理值相等规则时,则不应该再重载“==”运行符号,因为保持其缺省语义为判断引用相等才 是恰当的处理规则。

  Equals 虚方法与==操作符的主要区别在于多态表现:Equals 通过虚方法覆写来实现,而==操 作符则是通过运算符重载来实现,覆写和重载的区别请参考 1.4 节“多态的艺术”。

原文地址:https://www.cnblogs.com/kikyoqiang/p/10347669.html

时间: 2024-08-06 20:26:58

8.2.2 本质分析的相关文章

第51课 继承对象模型分析——多态的本质分析

多态的本质分析 用C写面向对象,用C实现多态 #ifndef _51_2_H_ #define _51_2_H_ typedef void Demo; typedef void Derived; Demo* Demo_Create(int i, int j); int Demo_GetI(Demo* pThis); int Demo_GetJ(Demo* pThis); int Demo_Add(Demo* pThis, int value); void Demo_Free(Demo* pThi

第27课 数组的本质分析

1. 数组的概念 (1)数组是相同类型的变量的有序集合 (2)数组在一片连续的内存空间中存储元素 (3)数组元素的个数可以显示或隐式指定 [编程实验]数组的初始化 #include <stdio.h> int main(){ //数组初始化 int a[5] = {1, 2};//第1.2个元素分别为1.2,其余为0 int b[] = {1, 2};//编译过程中,编译会为计算数组b的大小 int i=0; for (i = 0;i < 4;i++) { printf("a[

C++中引用的本质分析

引用的意义 引用作为变量别名而存在,因此在一些场合可以代替指针 引用相对于指针来说具有更好的可读性和实用性 swap函数的实现对比: void swap(int* a, int* b) { int t = *a; *a = *b; *b = t; } void swap_yinyong(int& a,int& b) { int t = a; a = b; b = t; } int main() { int a = 1; int b = 2; printf("a = %d,b =

应聘条件本质分析

太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 专栏申请有一段时间了,不过一直没有想好,如何去写相应的内容. 想法源于之前招聘的过程中,很多开发人员总有这样或那样的局限,尤其是,对于初级要求,往

指针的本质分析

*号的意义 在指针声明时,*号表示所声明的变量为指针 在指针使用时,*号表示取指针所指向的内存空间中的值 指针是变量,因此可以声明指针参数 当一个函数体内部需要改变实参的值,则需要使用指针参数 函数调用时实参值将复制到形参 指针适用于复杂数据类型作为参数的函数中 const int* p; //p可变,p指向的内容不可变 int const* p; //p可变,p指向的内容不可变 int* const p; //p不可变,p指向的内容不可变 const int* const p; //p和p指向

数组的本质分析

数组是相同类型的变量的有序集合 数组在一片连续的内存空间中存储元素 数组元素的个数可以显示或隐式指定 int a[5] = {1,2}; int b[] = {1,2}; 数组名代表数组首元素的地址 数组的地址需要取地址符&才能得到 数组首元素的地址与数组的地址值相同 数组首元素的地址与数组的地址是两个不同的概念 数组名可以看作一个常量指针 数组名"指向"的是内存中数组首元素的起始位置 数组名不包含数组的长度信息 在表达式中数组名只能作为右值使用 只有在下列场合中数组名不能看做

第5课 引用的本质分析

引用作为变量别名而存在,因此在一些场合可以代替指针 引用相对于指针来说具有更好的可读性和实用性 swap函数的实现对比如下: 注意: 函数中的引用形参不需要进行初始化. 示例程序如下: 形参没有初始化,而是在第15行调用的时候对引用形参进行初始化. const引用: 当使用常量对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名. 例: 结论: 使用常量对const引用初始化后将生成一个只读变量.普通引用不能使用常量值进行初始化,但是const引用可以. 我

C/C++ 变量的本质分析

1. 程序通过变量来申请和命名内存空间. int a=0; //申请一个大小为int型的内存空间,这个内存空间取名叫a,申请的内存空间里存储0 2. 可以通过变量名访问变量的内存空间. 3. 修改变量的方法: 直接修改: a =  1; 间接修改: 拿到变量的内存空间的地址编号,修改地址编号处的内存. 通过别名修改?? C++引用 //待补充. 4. 数据类型和变量的关系: 变量需要通过类型来定义.编译器需要知道变量类型才能为变量分配内存空间. 5. 总结: 对内存,可读可写; 通过变量读写数据

数组本质分析

  数组是相同类型变量的有序集合,例如:int a[5]; a代表数组第一个元素的起始地址 有5个int类型的数据,且是5个连续的内存空间. 这个20个字节的空间的名字叫a,a[0],a[1]都是数组中的元素,数组中的元素没有名字 数组的大小 数组在一片连续的内存空间中存储元素 数组元素的个数可以显式或隐式指定:int a[5] = {2,3}; int b[] = {1,2}; 1 #include <stdio.h> 2 int main() 3 { 4 int a[5] = {1,2};