zoukankan      html  css  js  c++  java
  • Effective C# Item9:理解几个相等判断之间的关系

        C#中,有四种方式可以应用于“相等判断”,如下。

    代码
    public static bool ReferenceEquals( object left, object right );
    public static bool Equals( object left, object right );
    public virtual bool Equals( object right);
    public static bool operator==( MyClass left, MyClass right );
        对于一个判断是否相等的操作,为什么会有四种形式呢,究其原因,还要看C#的数据类型,C#的数据类型分为值类型和引用类型,其中值类型直接存储在堆栈上,而引用类型的值存储在堆上,在栈中保留一个指向堆中地址的引用。这样在判断相等的时候,就会产生两种判断方式:1)判断变量在堆栈上存储的值是否相等,这对于值类型来说,就足够了,但是对于引用类型来说,只是判断了堆栈中保存的引用是否相等,还是不全面的;2)判断变量在堆中存储的值是否相等,这主要用于引用类型。

        关于如何判断“相等”,如果两个引用类型的变量指向同一个对象,那么它们将被认为是“引用相等”;如果两个值类型的变量类型相同,而且包含同样的内容,它们被认为是“值相等”。

        对于上述提供的四种用于判断“相等”的方法,其中前两种都是Object类带有的静态方法,其中Object.ReferenceEquals()方法用于判断两个变量的对象标识是否相同,不论是值类型还是引用类型,都是判断是否“引用相等”,而不是“值相等”,这意味着如果我们对于两个值类型使用该方法,那么总是会返回false。

        上述描述的第二种方式,是Object类型的静态方法Equals(),当我们不知道两个变量的运行时类型时,可以使用这个方法来判断两个变量是否相等,由于刚方法并不知道变量的类型,因此,“相等判断”的操作是依赖于类型的,即它会调用其中一个对象实例的Equals方法。静态Object.Equals()方法的实现如下。

    代码
    1 public static bool Equals( object left, object right )
    2 {
    3 // Check object identity
    4   if (left == right )
    5 return true;
    6 // both null references handled above
    7 if ((left == null) || (right == null))
    8 return false;
    9 return left.Equals (right);
    10 }

        上述第三种方式,是对象实例的Equals()方法,其中System.Object类作为所有类的基类,本身也定义了Equals()方法,Object实例中的Equals()方法,是判断“引用相等”,其行为和ReferenceEquals()方式完全一样。而System.ValueType作为所有值类型的基类,它重写了Equals()方法,在重写方法中,是按照“值相等”的方式来进行判断的,但是,ValueType重写的Equals()方法效率不高,原因是它使用了反射来得到对象的所有属性,进而判断属性的值是否相同,这样会导致性能很差,因此,当我们定义一个值类型时,应该总是重写Equals()方法。

        一般,我们重写Equals()方法的形式如下。

    代码
    1 public class Foo
    2 {
    3 public override bool Equals( object right )
    4 {
    5 // check null:
    6 // the this pointer is never null in C# methods.
    7 if (right == null)
    8 return false;
    9
    10 if (object.ReferenceEquals( this, right ))
    11 return true;
    12
    13 // Discussed below.
    14 if (this.GetType() != right.GetType())
    15 return false;
    16
    17 // Compare this type's contents here:
    18 return CompareFooMembers(
    19 this, right as Foo );
    20 }
    21 }
    22
    23

        我们在重写Equals()方法时,应该遵循以下三个原则:

    1. 自反性,即a=a
    2. 交换性,即如果a=b,那么b=a
    3. 传递性,即如果a=b,b=c,那么a=c

        当我们在一个有类继承层次关系的结构中,为父类和子类都重写Equals()方法, 那么很可能造成非常诡异的Bug,我们来看下面的代码。

    代码
    1 public class B
    2 {
    3 public override bool Equals( object right )
    4 {
    5 // check null:
    6 if (right == null)
    7 return false;
    8
    9 // Check reference equality:
    10 if (object.ReferenceEquals( this, right ))
    11 return true;
    12
    13 // Problems here, discussed below.
    14 B rightAsB = right as B;
    15 if (rightAsB == null)
    16 return false;
    17
    18 return CompareBMembers( this, rightAsB );
    19 }
    20 }
    21
    22 public class D : B
    23 {
    24 // etc.
    25 public override bool Equals( object right )
    26 {
    27 // check null:
    28 if (right == null)
    29 return false;
    30
    31 if (object.ReferenceEquals( this, right ))
    32 return true;
    33
    34 // Problems here.
    35 D rightAsD = right as D;
    36 if (rightAsD == null)
    37 return false;
    38
    39 if (base.Equals( rightAsD ) == false)
    40 return false;
    41
    42 return CompareDMembers( this, rightAsD );
    43 }
    44
    45 }
    46
    47 //Test:
    48 B baseObject = new B();
    49 D derivedObject = new D();
    50
    51 // Comparison 1.
    52 if (baseObject.Equals(derivedObject))
    53 Console.WriteLine( "Equals" );
    54 else
    55 Console.WriteLine( "Not Equal" );
    56
    57 // Comparison 2.
    58 if (derivedObject.Equals(baseObject))
    59 Console.WriteLine( "Equals" );
    60 else
    61 Console.WriteLine( "Not Equal" );
    62
    63
        如果你认为上述代码应该返回两个“Equals”或者两个“Not Equals”,那么无可厚非,但实际上,对于上述的两次比较,第二次总是会返回false,而第一次有时会返回true,有时会返回false。原因在于类型转换,子类型是可以默认转换为父类型的,但是父类型不可以转换为子类型。

        因此,当我们重写Equals()方法时,有一个很好的建议:如果基类的Equals()方法不是由System.Object或者System.ValueType提供的话,那么我们也应该在重写子类的Equals()方法时,调用基类的Equals()方法。

        关于上述判断“相等”的方式,总结如下:

    1. 永远不要重写Object类的ReferenceEquals()和Equals()两个静态方法。
    2. 对于值类型来说,为了提高效率,我们应该总是重写实例的Equals()方法和==()操作符,对于引用类型,如果我们认为相等的含义并非是对象标识相同的话,那么也需要重写Equals()方法,但是不应该重写==()操作符,.NET建议所有引用类型上应用==操作时,都遵循“引用相等”。
  • 相关阅读:
    (九)SpringBoot之错误处理
    (九)SpringBoot之使用jsp
    (八)SpringBoot之freeMarker基本使用
    (七)freemarker的基本语法及入门基础
    (六)Spring Boot之日志配置-logback和log4j2
    (五)Spring Boot之@RestController注解和ConfigurationProperties配置多个属性
    (四)Spring Boot之配置文件-多环境配置
    HashPayloadPcapReader
    Wireshark理解TCP乱序重组和HTTP解析渲染
    Centos定时启动和清除任务
  • 原文地址:https://www.cnblogs.com/wing011203/p/1642906.html
Copyright © 2011-2022 走看看