考虑以下代码:
class MyType : TypeDelegator
{
public MyType(Type parent)
: base(parent)
{
}
}
class Program
{
static void Main(string[] args)
{
Type t1 = typeof(string);
Type t2 = new MyType(typeof(string));
Console.WriteLine(EqualityComparer<Type>.Default.Equals(t1, t2)); // <-- false
Console.WriteLine(EqualityComparer<Type>.Default.Equals(t2, t1)); // <-- true
Console.WriteLine(t1.Equals(t2)); // <-- true
Console.WriteLine(t2.Equals(t1)); // <-- true
Console.WriteLine(Object.Equals(t1, t2)); // <-- false
Console.WriteLine(Object.Equals(t2, t1)); // <-- true
}
}
各种版本的Equals如何返回不同的结果? EqualityComparer.Default可能调用Object.Equals,因此这些结果匹配,尽管它们本身不一致。并且Equals的普通实例版本都返回
true
。当让一个方法返回实际上继承自
Type
的TypeDelegator
时,这显然会产生问题。想象一下,例如将这些类型作为键放在字典中,默认情况下,使用EqualityComparer.Default进行比较。有什么办法解决这个问题?我希望上面代码中的所有方法都返回
true
。 最佳答案
更新
该答案的代码已成为GitHub上的存储库:Undefault.NET on GitHub
史蒂文(Steven)很好地解释了为什么这样做会起作用。我不认为Object.Equals
案例有解决方案。然而,
我找到了一种方法,通过使用反射配置默认的相等比较器来解决EqualityComparer<T>.Default
情况下的问题。
每个应用程序生命周期仅需要发生一次这种小小的黑客攻击。启动将是执行此操作的好时机。使它起作用的代码行是:
DefaultComparisonConfigurator.ConfigureEqualityComparer<Type>(new HackedTypeEqualityComparer());
执行该代码后,
EqualityComparer<Type>.Default.Equals(t2, t1))
将产生与EqualityComparer<Type>.Default.Equals(t1,t2))
相同的结果(在您的示例中)。支持的基础结构代码包括:
1.自定义
IEqualityComparer<Type>
实现此类按照您希望其行为的方式处理相等性比较。
public class HackedTypeEqualityComparer : EqualityComparer<Type> {
public override bool Equals(Type one, Type other){
return ReferenceEquals(one,null)
? ReferenceEquals(other,null)
: !ReferenceEquals(other,null)
&& ( (one is TypeDelegator || !(other is TypeDelegator))
? one.Equals(other)
: other.Equals(one));
}
public override int GetHashCode(Type type){ return type.GetHashCode(); }
}
2. Configurator类
此类使用反射来配置
EqualityComparer<T>.Default
的基础字段。另外,此类提供了一种机制来操纵Comparer<T>.Default
的值,并确保所配置的实现的结果兼容。还有一种方法可以将配置恢复为框架默认值。public class DefaultComparisonConfigurator
{
static DefaultComparisonConfigurator(){
Gate = new object();
ConfiguredEqualityComparerTypes = new HashSet<Type>();
}
private static readonly object Gate;
private static readonly ISet<Type> ConfiguredEqualityComparerTypes;
public static void ConfigureEqualityComparer<T>(IEqualityComparer<T> equalityComparer){
if(equalityComparer == null) throw new ArgumentNullException("equalityComparer");
if(EqualityComparer<T>.Default == equalityComparer) return;
lock(Gate){
ConfiguredEqualityComparerTypes.Add(typeof(T));
FieldFor<T>.EqualityComparer.SetValue(null,equalityComparer);
FieldFor<T>.Comparer.SetValue(null,new EqualityComparerCompatibleComparerDecorator<T>(Comparer<T>.Default,equalityComparer));
}
}
public static void ConfigureComparer<T>(IComparer<T> comparer){
if(comparer == null) throw new ArgumentNullException("comparer");
if(Comparer<T>.Default == comparer) return;
lock(Gate){
if(ConfiguredEqualityComparerTypes.Contains(typeof(T)))
FieldFor<T>.Comparer.SetValue(null,new EqualityComparerCompatibleComparerDecorator<T>(comparer,EqualityComparer<T>.Default));
else
FieldFor<T>.Comparer.SetValue(null,comparer);
}
}
public static void RevertConfigurationFor<T>(){
lock(Gate){
FieldFor<T>.EqualityComparer.SetValue(null,null);
FieldFor<T>.Comparer.SetValue(null,null);
ConfiguredEqualityComparerTypes.Remove(typeof(T));
}
}
private static class FieldFor<T> {
private const string FieldName = "defaultComparer";
private const BindingFlags FieldBindingFlags = BindingFlags.NonPublic|BindingFlags.Static;
static FieldInfo comparer, equalityComparer;
public static FieldInfo Comparer { get { return comparer ?? (comparer = typeof(Comparer<T>).GetField(FieldName,FieldBindingFlags)); } }
public static FieldInfo EqualityComparer { get { return equalityComparer ?? (equalityComparer = typeof(EqualityComparer<T>).GetField(FieldName,FieldBindingFlags)); } }
}
}
3.兼容的
IComparer<T>
实现基本上,这是
IComparer<T>
的装饰器,可确保在注入(inject)Comparer<T>
时EqualityComparer<T>
和EqualityComparer<T>
之间具有兼容性。确保已配置的IEqualityComparer<T>
实现实现认为相等的任何两个值始终具有0
的比较结果。public class EqualityComparerCompatibleComparerDecorator<T> : Comparer<T> {
public EqualityComparerCompatibleComparerDecorator(IComparer<T> comparer, IEqualityComparer<T> equalityComparer){
if(comparer == null) throw new ArgumentNullException("comparer");
if(equalityComparer == null) throw new ArgumentNullException("equalityComparer");
this.comparer = comparer;
this.equalityComparer = equalityComparer;
}
private readonly IComparer<T> comparer;
private readonly IEqualityComparer<T> equalityComparer;
public override int Compare(T left, T right){ return this.equalityComparer.Equals(left,right) ? 0 : comparer.Compare(left,right); }
}