C#如何重写Equals

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< T >接口的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进行查找时,不能找到对应的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值