由于您提到了不仅要实现 Equals
,还要实现 GetHashCode
,这意味着您手上的是不可变的 struct
s 或 class
es。
您可能也知道反射比手写代码慢一点,并且您知道您的情况完全符合 95% 的不需要考虑性能的情况。
在这种情况下,您绝对应该使用反射实现 Equals
和 GetHashCode
。
原因如下:
- 这将使您免于编写大量愚蠢重复的代码,并且
- 它保证处理所有字段,而手动编写的代码可能会无意中省略某些字段,从而导致非常隐蔽的错误。
但是,这很棘手。
以下是确实有效的方法。
查看代码中的“PEARL”注释,以了解为什么它很棘手。
注意:您可以自己进行哈希码计算,但为了方便起见,我使用了
System.HashCode
。为了使这种便利对您可用,请使用NuGet添加对包
Microsoft.Bcl.HashCode
的引用。
类
ReflectionHelpers
#nullable enable
using Sys = System;
using SysReflect = System.Reflection;
public static bool MemberwiseEquals<T>( T a, object? b ) where T : notnull
{
if( b == null )
return false;
if( ReferenceEquals( a, b ) )
return true;
Sys.Type type = typeof(T);
Assert( a.GetType() == type );
Assert( b.GetType() == type );
foreach( SysReflect.FieldInfo fieldInfo in type.GetFields(
SysReflect.BindingFlags.Instance |
SysReflect.BindingFlags.Public |
SysReflect.BindingFlags.NonPublic ) )
{
object? value1 = fieldInfo.GetValue( a );
object? value2 = fieldInfo.GetValue( b );
if( fieldInfo.FieldType.IsPrimitive )
{
if( !value1.Equals( value2 ) )
return false;
}
else
{
if( !DotNetHelpers.Equals( value1, value2 ) )
return false;
}
}
return true;
}
public static int MemberwiseGetHashCode<T>( T obj ) where T : notnull
{
Sys.Type type = typeof(T);
Assert( obj.GetType() == type );
Sys.HashCode hashCodeBuilder = new Sys.HashCode();
foreach( SysReflect.FieldInfo fieldInfo in type.GetFields(
SysReflect.BindingFlags.Instance |
SysReflect.BindingFlags.Public |
SysReflect.BindingFlags.NonPublic ) )
{
Assert( fieldInfo.IsInitOnly );
object? fieldValue = fieldInfo.GetValue( obj );
hashCodeBuilder.Add( fieldValue );
}
return hashCodeBuilder.ToHashCode();
}
类 DotNetHelpers
using Sys = System;
using Legacy = System.Collections;
public new static bool Equals( object? a, object? b )
{
if( ReferenceEquals( a, b ) )
return true;
if( a == null || b == null )
return false;
if( a.Equals( b ) )
return true;
if( a is Legacy.IEnumerable enumerableA && b is Legacy.IEnumerable enumerableB )
return legacyEnumerablesEqual( enumerableA, enumerableB );
return false;
static bool legacyEnumerablesEqual( Legacy.IEnumerable a, Legacy.IEnumerable b )
{
Legacy.IEnumerator enumerator1 = a.GetEnumerator();
Legacy.IEnumerator enumerator2 = b.GetEnumerator();
try
{
while( enumerator1.MoveNext() )
{
if( !enumerator2.MoveNext() )
return false;
if( !Equals( enumerator1.Current, enumerator2.Current ) )
return false;
}
if( enumerator2.MoveNext() )
return false;
return true;
}
finally
{
(enumerator1 as Sys.IDisposable)?.Dispose();
(enumerator2 as Sys.IDisposable)?.Dispose();
}
}
}
请按以下方式使用:
类 MyClass
public override bool Equals( object other ) => other is MyClass kin && Equals( kin );
public bool Equals( MyClass other ) => ReflectionHelpers.MemberwiseEquals( this, other );
public override int GetHashCode() => ReflectionHelpers.MemberwiseGetHashCode( this );
GetHashCode
和Equals
的算法都等同于上述算法。另外,发布的代码存在几个问题。您可能会在多个位置取消引用null
。此外,您的Equals
版本在相应属性之间使用引用相等性,这不是最常见的习惯用法。 - AniaManager.Equals(anEmployee)
将测试不同于anEmployee.Equals(aManager)
的属性)。此外,当你执行anApple.Equals(anOrange)
时会发生什么?我没有测试过,但似乎在使用不相关类型调用 Equals 时会出现一些奇怪的行为。 - Chris Shaffer