zoukankan      html  css  js  c++  java
  • [c#基础]值类型和引用类型的Equals,==的区别

    引言

    最近一个朋友正在找工作,他说在笔试题中遇到Equals和==有什么区别的题,当时跟他说如果是值类型的,它们没有区别,如果是引用类型的有区别,但string类型除外。为了证实自己的说法,也研究了一下,以免误导别人,这里将研究结果总结一下,如果我有什么地方说的不对的地方,望指出。

    相等性

    在定义类或结构时,您将决定为类型创建值相等性(或等效性)的自定义定义是否有意义。 通常,当类型的对象预期要添加到某类集合时,或者当这些对象主要用于存储一组字段或属性时,您将实现值相等性。 您可以基于类型中所有字段和属性的比较来定义值相等性,也可以基于子集进行定义。 但在任何一种情况下,类和结构中的实现均应遵循五个等效性保证条件:

    1. x.Equals(x) 返回 true. 。这称为自反属性。

    2. x.Equals(y) 返回与 Equals(x) 相同的值。 这称为对称属性。

    3. 如果 (x.Equals(y) && y.Equals(z)) 返回 true,则 x.Equals(z) 返回 true。 这称为可传递属性。

    4. 只要不修改 x 和 y 所引用的对象,x.Equals(y) 的后续调用就返回相同的值。

    5. x.Equals(null) 返回 false。 但是,null.Equals(null) 会引发异常;它不遵循上面的第二条规则。

    您定义的任何结构已经具有它从 Object.Equals(Object) 方法的 System.ValueType 重写中继承的默认值相等性实现。 此实现使用反射来检查类型中的所有公共和非公共字段以及属性。 尽管此实现可生成正确的结果,但与您专门为类型编写的自定义实现相比,它的速度相对较慢。
    类和结构的值相等性的实现详细信息不同。 但是,类和结构都需要相同的基础步骤来实现相等性:
    重写 Object.Equals(Object)虚方法。 大多数情况下,您的 bool Equals( object obj ) 实现应只调入作为 System.IEquatable<T> 接口的实现的类型特定 Equals 方法。 (请参见步骤 2。)
    通过提供类型特定的 Equals 方法实现 System.IEquatable<T> 接口。 实际的等效性比较将在此接口中执行。 例如,您可能决定通过仅比较类型中的一两个字段来定义相等性。 不要从 Equals 中引发异常。 仅适用于类:此方法应仅检查类中声明的字段。 它应调用 base.Equals 来检查基类中的字段。 (如果类型直接从 Object 中继承,则不要这样做,因为 Object.Equals(Object) 的 Object 实现会执行引用相等性检查。)
    可选,但建议这样做:重载 == 和 != 运算符。
    重写 Object.GetHashCode,使具有值相等性的两个对象生成相同的哈希代码。
    可选:若要支持“大于”或“小于”定义,请为类型实现 IComparable<T> 接口,并同时重载 <= 和 >= 运算符。

                             ——MSDN(http://msdn.microsoft.com/zh-cn/library/dd183755.aspx)这里将msdn的说法贴在此处,方便查看。

    值类型

    这里就以int类型的为代表进行分析,在分析之前先复习一下什么是重载?重载:简单的说就是一个类中的方法与另一个方法同名,但是参数列表个数或者类型或者返回值类型不同,则这两个方法构成重载。那么重载方法的调用规则是什么?那么先看下面的一段测试代码:

     1 namespace Wolfy.EqualsDemo
     2 {
     3     class Program
     4     {
     5         static void Main(string[] args)
     6         {
     7             int a =1, b = 1;
     8             Console.WriteLine(Add(a,b));
     9             Console.Read();
    10         }
    11         static int Add(object a, object b)
    12         {
    13             Console.WriteLine("调用了object类型参数列表的方法:");
    14             return (int)a + (int)b;
    15         }
    16         static int Add(int a, float b)
    17         {
    18             Console.WriteLine("调用了int,float类型参数列表的方法:");
    19             return a + (int)b;
    20         }
    21         static int Add(int a, int b)
    22         {
    23             Console.WriteLine("调用了int类型参数列表的方法:");
    24             return a + b;
    25         }
    26     }
    27 }

    测试结果:

    说明根据传入实参的类型,优先匹配最相近的形参列表的方法。

    那么我们将Add(int a ,int b)这个方法注释掉,那么会调用哪个方法?

    为什么花费那么多口舌说明上面的问题,那么现在看一下Int32反编译的代码:

    Int32有一个自己的Equals方法,有一个重写的Equals方法,如果两个int类型的值进行比较,Equals和==是一样的,因为它优先调用了下面的Equals方法,如果是下面的代码,则会选择重写的Equals方法。

    1         static void Main(string[] args)
    2         {
    3             int a = 1;
    4             object b = 1;
    5             Console.WriteLine(a.Equals(b));
    6             Console.Read();
    7         }

    可见,对于值类型的Equals和==是一样的。

    引用类型

    在类(引用类型)上,两种 Object.Equals(Object) 方法的默认实现均执行引用相等性比较,而不是值相等性检查。 当实施者重写虚方法时,目的是为了为其指定值相等性语义。
    即使类不重载 == 和 != 运算符,也可以将这些运算符与类一起使用。 但是,默认行为是执行引用相等性检查。 在类中,如果您重载 Equals 方法,则应重载 == 和 != 运算符,但这并不是必需的。

                                                                                                                ——MSDN

    测试代码:

     1  static void Main(string[] args)
     2         {
     3 
     4             Person p1 = new Person() { Name = "wolfy" };
     5             Person p2 = new Person() { Name = "wolfy" };
     6             Person p3 = p2;
     7             bool r1 = p1 == p2;
     8             bool r2 = p1.Equals(p2);
     9             bool r3 = p2 == p3;
    10             bool r4 = p2.Equals(p3);
    11             bool r5 = object.ReferenceEquals(p1, p2);
    12             bool r6 = object.Equals(p1,p2);
    13             bool r7 = object.ReferenceEquals(p2, p3);
    14             Console.WriteLine("==	"+r1);
    15             Console.WriteLine("Equals	"+r2);
    16             Console.WriteLine("p3=p2	"+r3);
    17             Console.WriteLine("p2.Equals(p3)	"+r4);
    18             Console.WriteLine("object.ReferenceEquals	" + r5);
    19             Console.WriteLine("object.Equals(p1,p2)	" + r6);
    20             Console.WriteLine("object.ReferenceEquals(p2, p3)	" + r7);
    21             Console.Read();
    22         }

    结果:

    顺便反编译一下Equals和ReferenceEquals方法,看看他们的实现如何?

    1 // object
    2 [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
    3 public static bool Equals(object objA, object objB)
    4 {
    5     return objA == objB || (objA != null && objB != null && objA.Equals(objB));
    6 }
    1 // object
    2 [__DynamicallyInvokable, ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
    3 public static bool ReferenceEquals(object objA, object objB)
    4 {
    5     return objA == objB;
    6 }

    通过上面的代码,我们可以得出这样的结论,引用类型中Equals和ReferenceEquals的行为是相同的,==与ReferenceEquals的行为也相同,但string除外。

     对特殊应用类型string的相等性,遵循值类型的相等性。string类型的反编译后的Equals方法和==代码如下:

      1         public override bool Equals(object obj)
      2         {
      3             if (this == null)
      4             {
      5                 throw new NullReferenceException();
      6             }
      7             string text = obj as string;
      8             return text != null && (object.ReferenceEquals(this, obj) || (this.Length == text.Length && string.EqualsHelper(this, text)));
      9         }
     10         [__DynamicallyInvokable, ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
     11         public bool Equals(string value)
     12         {
     13             if (this == null)
     14             {
     15                 throw new NullReferenceException();
     16             }
     17             return value != null && (object.ReferenceEquals(this, value) || (this.Length == value.Length && string.EqualsHelper(this, value)));
     18         }
     19         [__DynamicallyInvokable, SecuritySafeCritical]
     20         public bool Equals(string value, StringComparison comparisonType)
     21         {
     22             if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
     23             {
     24                 throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
     25             }
     26             if (this == value)
     27             {
     28                 return true;
     29             }
     30             if (value == null)
     31             {
     32                 return false;
     33             }
     34             switch (comparisonType)
     35             {
     36             case StringComparison.CurrentCulture:
     37                 return CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0;
     38             case StringComparison.CurrentCultureIgnoreCase:
     39                 return CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0;
     40             case StringComparison.InvariantCulture:
     41                 return CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0;
     42             case StringComparison.InvariantCultureIgnoreCase:
     43                 return CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0;
     44             case StringComparison.Ordinal:
     45                 return this.Length == value.Length && string.EqualsHelper(this, value);
     46             case StringComparison.OrdinalIgnoreCase:
     47                 if (this.Length != value.Length)
     48                 {
     49                     return false;
     50                 }
     51                 if (this.IsAscii() && value.IsAscii())
     52                 {
     53                     return string.CompareOrdinalIgnoreCaseHelper(this, value) == 0;
     54                 }
     55                 return TextInfo.CompareOrdinalIgnoreCase(this, value) == 0;
     56             default:
     57                 throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
     58             }
     59         }
     60         [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
     61         public static bool Equals(string a, string b)
     62         {
     63             return a == b || (a != null && b != null && a.Length == b.Length && string.EqualsHelper(a, b));
     64         }
     65         [__DynamicallyInvokable, SecuritySafeCritical]
     66         public static bool Equals(string a, string b, StringComparison comparisonType)
     67         {
     68             if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
     69             {
     70                 throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
     71             }
     72             if (a == b)
     73             {
     74                 return true;
     75             }
     76             if (a == null || b == null)
     77             {
     78                 return false;
     79             }
     80             switch (comparisonType)
     81             {
     82             case StringComparison.CurrentCulture:
     83                 return CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0;
     84             case StringComparison.CurrentCultureIgnoreCase:
     85                 return CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0;
     86             case StringComparison.InvariantCulture:
     87                 return CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0;
     88             case StringComparison.InvariantCultureIgnoreCase:
     89                 return CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0;
     90             case StringComparison.Ordinal:
     91                 return a.Length == b.Length && string.EqualsHelper(a, b);
     92             case StringComparison.OrdinalIgnoreCase:
     93                 if (a.Length != b.Length)
     94                 {
     95                     return false;
     96                 }
     97                 if (a.IsAscii() && b.IsAscii())
     98                 {
     99                     return string.CompareOrdinalIgnoreCaseHelper(a, b) == 0;
    100                 }
    101                 return TextInfo.CompareOrdinalIgnoreCase(a, b) == 0;
    102             default:
    103                 throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
    104             }
    105         }
    106         [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
    107         public static bool operator ==(string a, string b)
    108         {
    109             return string.Equals(a, b);
    110         }
    111         [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
    112         public static bool operator !=(string a, string b)
    113         {
    114             return !string.Equals(a, b);
    115         }

    从上面的代码可以看出string类型的Equals和==是一样的。

    1     public static bool operator ==(string a, string b)
    2         {
    3             return string.Equals(a, b);
    4         }
    5 [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
    6         public static bool operator !=(string a, string b)
    7         {
    8             return !string.Equals(a, b);
    9         }

    总结

    值类型具有它从 Object.Equals(Object) 方法的 System.ValueType 重写中继承的默认值相等性实现。特殊的引用类型string类型,因为重写了Equals和==方法,所以string类型的Equals和==与值类型的相等性一样。对于其他的引用类型此时的Equals和==与引用相等ReferenceEquals的行为相同。

    以上是由一个同事的问题引起,中间也查了很多资料,发现这篇文章在草稿箱中躺了很久了,今天突然看到就拿出来晒晒。中间修修改改,总尝试着用哪种方式来说明这个老生常谈的问题更好些。以上有些观点,纯属个人见解,如果你有更好的理解方式,不妨分享一下。如果对你有所帮助,不妨点一下推荐,让更多的人看到,说说自己对Equals和==的理解。

  • 相关阅读:
    Mdate时间插件
    JS数组映射保存数据-场景
    基于微信的图片放大预览
    移动前端自适应布局适配解决方案
    JS数组映射详解
    回复与发表切换
    this应用详解-js原生
    淘宝虚拟产品自动发货软件
    搭建个人博客
    2019免杀大马
  • 原文地址:https://www.cnblogs.com/wolf-sun/p/3643863.html
Copyright © 2011-2022 走看看