C#如何重写Equals

标签: c#
50人阅读 评论(0) 收藏 举报
分类:

C#如何重写Equals

在自定义struct的时候,经常需要重写Equals方法,原因是一方面ValueType内部使用反射进行各字段相等判断,效率比较低;另一方面在调用基类Equals的时候,会造成值类型的装箱,(详细可以参考《值类型的拆箱与装箱》),除非能够确定类型不会进行相等性判断,否则建议进行Equals重写来提高性能。

同一性与相等性

同一性是指两个变量引用的是内存中的同一个对象,C#使用Object.ReferenceEquals(obj,obj)进行判断;
相等性是指两个变量所包含的数值相等,一般来说是对各个字段进行值比较。从概念上来说,同一性比相等性更加严格,满足同一性必然满足相等性,但符合相等性不一定满足同一性。

什么时候需要重写Equals

  1. 自定义结构体,处于性能考虑,避免不必要的装箱和反射。
  2. class类型需要相等性而非同一性的比较,例如System.String类型,虽然是引用类型,但是两个string变量进行比较时,是进行相等性比较,而非引用。

重写Equals遵守的规则

  1. Equals必须自反:x.Equals(x)肯定返回true。
  2. Equals必须对称:x.Equals(y)和y.Equals(x)必须返回相同的值。
  3. Equals必须可传递:x.Equals(y)返回true,y.Equals(z)返回true,则x.Equals(z)肯定返回true。
  4. Equals必须一致。比较的两个值不变,Equals返回也不能变。

重写Object.Equals流程

  1. 重写GetHashCode方法。
  2. 实现System.IEquatable接口的Equals方法,该接口允许定义类型安全的Equals方法。
  3. 重写Object.Equals,并在内部调用类型安全的Equals方法。
  4. 重载==和!=操作符,并在内部调用类型安全的Equals方法。

引用类型重写

这里实现Person类型的相等性重写,即如果Person.Id相等,则认为Person是相同的,如果需要进行同一性判断,需要使用Object.ReferenceEquals(obj,obj)进行判断。

internal class Program
{
    public static void Main(string[] args)
    {
        Person p1= new Person("1");
        Person p2= new Person("1");

        //True
        Console.WriteLine(p1==p2);

        //True
        Console.WriteLine(p1.Equals(p2));

        //False
        Console.WriteLine(ReferenceEquals(p1,p2));

        Console.Read();
    }
}

public class Person : IEquatable<Person>
 {
     private string _Id;

     public Person(string id)
     {
         _Id = id;
     }

     public bool Equals(Person other)
     {
         //this非空,obj如果为空,则返回false
         if (ReferenceEquals(null, other)) return false;

         //如果为同一对象,必然相等
         if (ReferenceEquals(this, other)) return true;

         //对比各个字段值
         if(!string.Equals(_Id, other._Id, StringComparison.InvariantCulture))
             return false;

         //如果基类不是从Object继承,需要调用base.Equals(other)
         //如果从Object继承,直接返回true
         return true;
     }

     public override bool Equals(object obj)
     {
         //this非空,obj如果为空,则返回false
         if (ReferenceEquals(null, obj)) return false;

         //如果为同一对象,必然相等
         if (ReferenceEquals(this, obj)) return true;

         //如果类型不同,则必然不相等
         if (obj.GetType() != this.GetType()) return false;

         //调用强类型对比
         return Equals((Person) obj);
     }

     //实现Equals重写同时,必须重写GetHashCode
     public override int GetHashCode()
     {
         return (_Id != null ? StringComparer.InvariantCulture.GetHashCode(_Id) : 0);
     }

     //重写==操作符
     public static bool operator ==(Person left, Person right)
     {
         return Equals(left, right);
     }

     //重写!=操作符
     public static bool operator !=(Person left, Person right)
     {
         return !Equals(left, right);
     }
 }

值类型重写

值类型必须实现相等性重写,因为两个值类型变量,不可能指向内存同一个对象,即Object.Reference(ValueType,ValueType)返回false。

internal class Program
{
    public static void Main(string[] args)
    {
        CustomStruct p1 = new CustomStruct("1");
        CustomStruct p2 = new CustomStruct("1");

        //True
        Console.WriteLine(p1 == p2);

        //True
        Console.WriteLine(p1.Equals(p2));

        //Always False
        Console.WriteLine(ReferenceEquals(p1,p2));

        Console.Read();
    }
}


public struct CustomStruct : IEquatable<CustomStruct>
{
    private readonly string _Id;

    public CustomStruct(string id)
    {
        _Id = id;
    }

    public bool Equals(CustomStruct other)
    {
        return string.Equals(_Id, other._Id, StringComparison.InvariantCulture);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        return obj is CustomStruct && Equals((CustomStruct) obj);
    }

    public override int GetHashCode()
    {
        return (_Id != null ? StringComparer.InvariantCulture.GetHashCode(_Id) : 0);
    }

    public static bool operator ==(CustomStruct left, CustomStruct right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(CustomStruct left, CustomStruct right)
    {
        return !left.Equals(right);
    }
}

关于GetHashCode

在重写Equals方法,而不重写GetHashCode方法,编译器会提示警告信息:“重写Object.Equals(object o)但不重写Object.GetHashCode()”,在使用Dictionary、HashMap类是,可能发生一些意想不到的Bug,还是针对上面的Person类(为了演示,先注释掉GetHashCode),编写一个示例来演示这种Bug。

using System;
using System.Collections.Generic;

internal class Program
{
    public static void Main(string[] args)
    {
        Dictionary<Person,string> dic = new Dictionary<Person, string>();

        Person p1 = new Person("Mike");
        Person p2 = new Person("Mike");

        dic.Add(p1,"p1 info");

        //True
        Console.WriteLine(p1==p2);

        //True - Mike在dic当中
        Console.WriteLine(dic.ContainsKey(p1));

        //False - Mike不在dic当中
        //这其实是不正确的,因为p1的值等于p2的值,将该值存入到dic当中,应该能够在利用该值再次读取出来
        Console.WriteLine(dic.ContainsKey(p2));

        Console.Read();
    }
}


public class Person : IEquatable<Person>
{
    private string _Id;

    public Person(string id)
    {
        _Id = id;
    }

    public bool Equals(Person other)
    {
        //this非空,obj如果为空,则返回false
        if (ReferenceEquals(null, other)) return false;

        //如果为同一对象,必然相等
        if (ReferenceEquals(this, other)) return true;

        //对比各个字段值
        if(!string.Equals(_Id, other._Id, StringComparison.InvariantCulture))
            return false;

        //如果基类不是从Object继承,需要调用base.Equals(other)
        //如果从Object继承,直接返回true
        return true;
    }

    public override bool Equals(object obj)
    {
        //this非空,obj如果为空,则返回false
        if (ReferenceEquals(null, obj)) return false;

        //如果为同一对象,必然相等
        if (ReferenceEquals(this, obj)) return true;

        //如果类型不同,则必然不相等
        if (obj.GetType() != this.GetType()) return false;

        //调用强类型对比
        return Equals((Person) obj);
    }

    //实现Equals重写同时,必须重写GetHashCode
    //这里先注释掉
    /*
    public override int GetHashCode()
    {
        return (_Id != null ? StringComparer.InvariantCulture.GetHashCode(_Id) : 0);
    }
    */

    //重写==操作符
    public static bool operator ==(Person left, Person right)
    {
        return Equals(left, right);
    }

    //重写!=操作符
    public static bool operator !=(Person left, Person right)
    {
        return !Equals(left, right);
    }
}

造成这种问题的原因在于,在基于Key-Value的集合当中,会根据Key值来查找Value值,CLR内部会优化这种查找,实际上,最终是根据Key值得HashCode来查找Value值,示例当中p1虽然和p2值相等,但是Person类有没重写GetHashCode,所以最终调用System.Object的GetHashCode实现,默认System.Object的GetHashCode为不同对象返回不同的值,所以在使用p2进行查找时,不能找到对应的值。

查看评论

邮件发送(C#)

-
  • 1970年01月01日 08:00

C#重写Equals()

之前讲过要重写Equals()就要重写GetHashCode(),这次说一下自己学到的如何重写Equals()。 首先,需要了解    “对象同一性”和“相等的对象”的含义区别 涉及到:相等的值类...
  • shenmegui_zyf
  • shenmegui_zyf
  • 2016-06-15 20:45:14
  • 1492

C# Equals的重写

来自:http://blog.sina.com.cn/s/blog_4baa3af301000a1e.html using System; using System.Collectio...
  • ArvinStudy
  • ArvinStudy
  • 2013-07-12 09:23:15
  • 1278

c# gethashcode和equal重写

重写一个类的 Equals方法,必须要重写它的gethashcode方法,在比较2个对象是否相等时,调用equals方法,2个对象可以有不同的hashcode,但Equals返回的却是TRUE,所以2...
  • shaojun5120
  • shaojun5120
  • 2014-03-06 19:58:42
  • 2891

重写equals方法

  • 2013年08月21日 12:21
  • 48KB
  • 下载

C#中包含子类集合的类的Equals方法重写

最近的项目中,对于单元测试,我们使用NMock来模拟测试方法中依赖的方法的行为。我们在定义模拟方法的参数值和返回值时遇到了问题。当参数或返回值为一个对象时,我们必须重写它的Equals方法来使模拟值与...
  • MaggieDorami
  • MaggieDorami
  • 2012-09-15 02:48:04
  • 2379

如何正确的重写equals() 和 hashCode()方法

equals和hashCode重写技巧
  • zzg1229059735
  • zzg1229059735
  • 2016-05-25 15:36:36
  • 12275

如何重写hashCode()和equals()方法

hashCode()和equals()方法可以说是Java完全面向对象的一大特色.它为我们的编程提供便利的同时也带来了很多危险.这篇文章我们就讨论一下如何正解理解和使用这2个方法. 如何重写equ...
  • tracker_w
  • tracker_w
  • 2013-12-02 12:57:13
  • 19997

重写equals方法判断类的值是否相等

public class Student {     private int id;     private String name;     private String className;...
  • u014538198
  • u014538198
  • 2015-03-30 10:51:42
  • 616

重写equals时还必须重写hashcode方法,详解

http://www.cnblogs.com/happyPawpaw/p/3744971.html http://www.jb51.net/article/42408.htm http:/...
  • basycia
  • basycia
  • 2016-07-31 22:03:55
  • 5459
    个人资料
    等级:
    访问量: 108
    积分: 76
    排名: 156万+