• C# 区别对待==和Equals,重写Equals


    CLR中将“相等性”分为两类:“值相等性”和“引用相等性”。

    值相等性:两个变量所包含的数值相等。

    引用相等性:两个变量引用的是内存中的同一个对象。

    无论是操作符“==”,还是方法“Equals()”,都倾向于表达这样一个原则:

    对于值类型,如果类型的值相等,就应该返回true;

    对于引用类型,如果类型指向同一个对象,则返回true。

    例:

     1     class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             int i = 1;
     6             int j = 1;
     7             Console.WriteLine(i == j);      //true
     8             Console.WriteLine(i.Equals(j)); //true
     9             j = i;
    10             Console.WriteLine(i == j);      //true
    11             Console.WriteLine(i.Equals(j)); //true
    12 
    13             object a = new Person("NO1");
    14             object b = new Person("NO1");
    15             Console.WriteLine(a == b);      //false
    16             Console.WriteLine(a.Equals(b)); //false
    17             b = a;
    18             Console.WriteLine(a == b);      //true
    19             Console.WriteLine(a.Equals(b)); //true
    20             Console.Read();
    21         }
    22     }
    23 
    24     class Person
    25     {
    26         public string ID { get; private set; }
    27         public Person(string value)
    28         {
    29             ID = value;
    30         }
    31     }

    操作符“==”,还是方法“Equals()”,都是可以被重载的。比如string类型,它是一个特殊的引用类型,微软觉得它的现实意义更接近于值类型,所以,再FCL中,string的比较被重载为针对”类型的值“的比较,而不是”引用本身“的比较。

    例:

    1             string s1 = "aa";
    2             string s2 = "aa";
    3             Console.WriteLine(s1 == s2);      //true
    4             Console.WriteLine(s1.Equals(s2)); //true
    5             s2 = s1;
    6             Console.WriteLine(s1 == s2);      //true
    7             Console.WriteLine(s1.Equals(s2)); //true

    从设计上来说,很多自定义类型(尤其是自定义的引用类型)会存在和string类型比较接近的情况。如第一个例子中的Person类,再现实生活中,如果两个人的ID是相等的,我们就会认为两者是同一个人,这个时候就要重载Equals方法了:

     1     class Person
     2     {
     3         public string ID { get; private set; }
     4         public Person(string value)
     5         {
     6             ID = value;
     7         }
     8         //重写Object.Equals(object o)
     9         public override bool Equals(object obj)
    10         {
    11             return ID == (obj as Person).ID;
    12         }
    13     }

    这时再去比较两个具有相同ID的Person对象的值就会返回true:

    1      object a = new Person("NO1");
    2      object b = new Person("NO1");
    3      Console.WriteLine(a == b);      //false
    4      Console.WriteLine(a.Equals(b)); //true

    此时,对于该类,可以用==判断两个实例是否为指向同一个对象,用Equals方法判断两个实例的值是否相等。

    注意:FCL中提供了Object.ReferenceEquals方法来明确肯定是比较”引用相等性“。

     但是,重写了Equals方法,编译器会提示一个信息:

    ”重写 Object.Equals(object o)  但不重写 Object.GetHashCode()”

    这样在使用FCL中的Dictionary类时,可能隐含一些潜在的Bug。

    在上面的代码基础下,增加PersonMoreInfo类:

    1     class PersonMoreInfo
    2     {
    3         public string Info { get; set; }
    4         public PersonMoreInfo(string value)
    5         {
    6             Info = value;
    7         }
    8     }

    创建一个Dictionary,通过key寻找value:

     1     class Program
     2     {
     3         static Dictionary<Person, PersonMoreInfo> PersonValues = new Dictionary<Person, PersonMoreInfo>();
     4         static void Main(string[] args)
     5         {
     6             AddPerson();
     7             Person mike = new Person("No2");
     8             Console.WriteLine(mike.GetHashCode());
     9             Console.WriteLine(PersonValues.ContainsKey(mike));//用key的HashCode寻找键
    10 
    11             Console.Read();
    12         }
    13         static void AddPerson()
    14         {
    15             Person mike = new Person("No2");
    16             PersonMoreInfo mikeValue = new PersonMoreInfo("Mike's info");
    17             PersonValues.Add(mike, mikeValue);
    18             Console.WriteLine(mike.GetHashCode());
    19             Console.WriteLine(PersonValues.ContainsKey(mike));
    20         }
    21     }

    运行结果:

    1 22008501
    2 True
    3 9008175
    4 False

    重写了Equals方法,所以在AddPerson方法里的mike和Main方法里的mike属于“值相等”,此时将“值”作为key放入Dictinoary中,再在某处根据mike将对应的键mikeValue取出来。但是上面代码运行结果却是Main方法中的mike没有对应的mikeValue。原因是键值对的集合会根据Key值的HashCode寻找Value值。CLR首先调用Person类的GetHashCode方法,因为Person类没有实现GetHashCode方法,所以就调用Object.GetHashCode()。Object为所有的CLR类型都提供了GetHashCode的默认实现,每new一个对象,CLR就会为该对象生成一个固定的整型值,该值在对象的生存周期内不会改变,GetHashCode方法就是实现对该整型值求HashCode。

    所有上面的代码中,两个mike对象的HashCode是不一样的,导致Main方法里的mike没有对应的mikeValue。此时就要重写GetHashCode方法:

     1     class Person
     2     {
     3         public string ID { get; private set; }
     4         public Person(string value)
     5         {
     6             ID = value;
     7         }
     8         //重写Object.Equals(object o)
     9         public override bool Equals(object obj)
    10         {
    11             return ID == (obj as Person).ID;
    12         }
    13         //重写Object.GetHashCode()
    14         public override int GetHashCode()
    15         {
    16             return ID.GetHashCode();
    17         }
    18     }

    再次运行代码得到以下结果:

    1 -54312782
    2 True
    3 -54312782
    4 True

    因为HashCode一般作为对象的特征,在对象生存周期内赋值就不应改变,所以因基于那些只读属性或特性生成HashCode。

    GetHashCode方法永远只返回一个整型int,而int的容量显然无法满足字符串的容量,以下代码会生成两个同样的HashCode:

    1       string s1 = "NB0903100006";
    2       string s2 = "NB0904140001";
    3       Console.WriteLine(s1.GetHashCode());
    4       Console.WriteLine(s2.GetHashCode());

    所以为了减少两个不同类型之间根据字符串产生相同的HashCode几率,要改进GetHashCode方法:

    1         //重写Object.GetHashCode()
    2         public override int GetHashCode()
    3         {
    4             return (System.Reflection.MethodBase.GetCurrentMethod().
    5                 DeclaringType.FullName + "#" + this.ID).GetHashCode();
    6         }

    注意重写Equals方法的同时,也应该实现一个类型安全的接口IEquatable<T>

     1     class Person : IEquatable<Person>
     2     {
     3         public string ID { get; private set; }
     4         public Person(string value)
     5         {
     6             ID = value;
     7         }
     8         //重写Object.Equals(object o)
     9         public override bool Equals(object obj)
    10         {
    11             return ID == (obj as Person).ID;
    12         }
    13         //重写Object.GetHashCode()
    14         public override int GetHashCode()
    15         {
    16             return (System.Reflection.MethodBase.GetCurrentMethod().
    17                 DeclaringType.FullName + "#" + this.ID).GetHashCode();
    18         }
    19         //重写IEquatable<Person>.Equals(T other)
    20         public bool Equals(Person other)
    21         {
    22             return ID == other.ID;
    23         }
    24     }

    参考:《编写高质量代码改善C#程序的157个建议》陆敏技

  • 相关阅读:
    Exadata 上关于SAS盘的小秘密
    Python 全栈开发:python循环语句while
    Python 全栈开发:python条件语句if..else..
    Python 全栈开发:python基础
    Python 全栈开发:python初识
    计算机基础(简单了解)
    如何计算网络配置中广播域和冲突域的数目?
    使VS开发的程序在Win7系统运行时自动提升权限
    在程序中通过Process启动外部exe的方法及注意事项
    获取指定目录下多种格式的文件
  • 原文地址:https://www.cnblogs.com/xuyouyou/p/13168783.html
Copyright © 2020-2023  润新知