实验代码
Son s1 = new(), s2 = new();
Console.WriteLine("_ = s1 == s2");
_ = s1 == s2;
Console.WriteLine();
Console.WriteLine("_ = s1 != s2;");
_ = s1 != s2;
Console.WriteLine();
Console.WriteLine("_ = s1.Equals(s2);");
_ = s1.Equals(s2);
Console.WriteLine();
Console.WriteLine("_ = ((object)s1).Equals(s2);");
_ = ((object)s1).Equals(s2);
Console.WriteLine();
Console.WriteLine("s1 is IEquatable<Father>");
Console.WriteLine(s1 is IEquatable<Father>);
Console.WriteLine();
Console.WriteLine("s1.GetHashCode()");
Console.WriteLine(s1.GetHashCode());
Console.WriteLine();
public class Father : IEquatable<Father>
{
public virtual int A { get; set; }
public virtual int B { get; set; }
public override bool Equals(object? obj)
{
Console.WriteLine("object Equals");
if (obj is not null and Father)
{
Father f = (Father)obj;
return f.A == A && f.B == B;
}
return false;
}
public override int GetHashCode()
{
Console.WriteLine("GetHashCode");
return 0;
}
public static bool operator ==(Father left, Father right)
{
Console.WriteLine("比较==");
return left.Equals(right);
}
public static bool operator !=(Father left, Father right)
{
Console.WriteLine("比较!=");
return !(left == right);
}
public bool Equals(Father? other)
{
Console.WriteLine("IEquatable Equals");
return ((object)this).Equals(other);
}
}
public class Son : Father
{
public override int A { get => base.A; set => base.A = value; }
}
运行结果
_ = s1 == s2
比较==
IEquatable Equals
object Equals
_ = s1 != s2;
比较!=
比较==
IEquatable Equals
object Equals
_ = s1.Equals(s2);
IEquatable Equals
object Equals
_ = ((object)s1).Equals(s2);
object Equals
s1 is IEquatable<Father>
True
s1.GetHashCode()
GetHashCode
0
在父类重写的 Equals 方法,继承的 IEquatable 接口,重写的 == ,!= 运算符,重写的 GetHashCode 方法在子类中仍然是表现为被重写的状态。
子类实现 IEquatable 接口
现在让子类也实现 IEquatable 接口
Son s1 = new(), s2 = new();
s1.Equals(s2);
public class Father : IEquatable<Father>
{
public virtual int A { get; set; }
public virtual int B { get; set; }
public override bool Equals(object? obj)
{
Console.WriteLine("object Equals");
if (obj is not null and Father)
{
Father f = (Father)obj;
return f.A == A && f.B == B;
}
return false;
}
public override int GetHashCode()
{
Console.WriteLine("GetHashCode");
return 0;
}
public static bool operator ==(Father left, Father right)
{
Console.WriteLine("比较==");
return left.Equals(right);
}
public static bool operator !=(Father left, Father right)
{
Console.WriteLine("比较!=");
return !(left == right);
}
public bool Equals(Father? other)
{
Console.WriteLine("IEquatable Equals");
return ((object)this).Equals(other);
}
}
public class Son : Father, IEquatable<Son>
{
public bool Equals(Son? other)
{
Console.WriteLine("IEquatable<Son>.Equals");
return ((object)this).Equals(other);
}
}
运行结果
IEquatable<Son>.Equals
object Equals
Equals 方法的重载
此时代码提示如下
子类实现接口后,调用的默认就是子类实现的 Equals 方法。也可以用强制转换调用父类的 Equals 方法
这里属于 Equals 方法的不同类型的重载。通过 public override bool Equals(object? obj)
重写的 Equals 方法也是一种重载。这个重载是每一个 object 的子类都有的,不重写这个方法时比较的是两个对象的引用是否相等。
所有判断相等性的运算符、方法必须一起重写
上面那些判断相等性的方法、运算符必须要么一起重写,要么全部不重写。实现了 IEquatable 接口后也要重写全部。如果不全部重写,这些不同的相等性判断方法返回结果不一致会造成混乱。
对象放到哈希表的过程
对象在放到哈希表里首先是计算哈希值,然后尝试放到哈希表中的对应位置。如果该位置已经有一个对象了,再调用 Equals 方法判断是不是同一个对象,如果是,就不再放一次了。如果不是,则说明发生了哈希冲突,此时会将新对象用链表挂在哈希表该位置。
重写 Equals 方法后,如果将对象放在哈希表里,必须重写 GetHashCode 方法
重写相等性判断方法时,如果想让对象能够在哈希表里正常工作,必须也重写 GetHashCode 方法。GetHashCode 方法原本是根据引用值生成哈希码,原本的相等性运算也是根据引用值。这种情况可以正常工作。但是,如果重写了 Equals 方法,不重写 GetHashCode 方法,会导致明明相等的两个对象返回的哈希值不同(因为他们的引用不同),于是哈希表中的两个位置储存了两个相等的对象。
重写 GetHashCode 的注意事项
- 不要使用会变化的字段生成哈希码。因为字段变化后,生成的哈希码不同,此时再调用 GetHashCode 方法得到的哈希码不同,这会导致字段变化后无法在哈希表中找到该对象了。
- 两个对象使用 Equals 方法判断相等性返回 true 时,GetHashCode 方法必须能够返回相同的值。否则会导致两个相等的对象具有不同的哈希值,这会导致哈希表中有重复的元素。