TypeDelegator等式不一致?

DeC*_*Caf 22 .net c# types equality equals

请考虑以下代码:

    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
       }
   }
Run Code Online (Sandbox Code Playgroud)

为什么各种版本的Equals会返回不同的结果?EqualityComparer.Default可能调用Object.Equals,因此这些结果匹配,尽管它们本身不一致.Equals的正常实例版本都返回true.

当一个方法返回一个Type实际继承的方法时,这显然会产生问题TypeDelegator.想象一下,例如将这些类型作为键放在字典中,默认情况下使用EqualityComparer.Default进行比较.

有什么方法可以解决这个问题吗?我想在上面的代码中的所有方法返回true.

Ste*_*ven 7

以下代码返回System.RuntimeType

Type t1 = typeof(string);
Run Code Online (Sandbox Code Playgroud)

如果你看一下Type的代码就有:

public override bool Equals(Object o)
{
    if (o == null) 
        return false;

    return Equals(o as Type); 
}
Run Code Online (Sandbox Code Playgroud)

但是,System.RuntimeType具有:

public override bool Equals(object obj) 
{
    // ComObjects are identified by the instance of the Type object and not the TypeHandle.
    return obj == (object)this;
} 
Run Code Online (Sandbox Code Playgroud)

如果您查看程序集,它会执行:cmp rdx,rcx,所以只需要直接内存比较.

您可以使用以下方法重现它:

bool a = t1.Equals((object)t2); // False
bool b = t1.Equals(t2); // True
Run Code Online (Sandbox Code Playgroud)

所以看起来RuntimeType重写了Type Equals方法来进行直接比较......看起来没有简单的解决方法(不提供比较器).

编辑添加:出于好奇,我看了一下RuntimeType的.NET 1.0和1.1实现.它们没有在RuntimeType中重写Equals,因此问题是在.NET 2.0中引入的.


sma*_*man 4

更新

此答案中的代码已成为 GitHub 上的存储库:GitHub 上的 Undefault.NET

史蒂文很好地解释了为什么它会这样工作。我不相信有解决方案Object.Equals。然而,

我已经找到了解决该问题的方法EqualityComparer<T>.Default通过反射配置默认相等比较器来解决此问题的方法。

这个小技巧在每个应用程序生命周期只需要发生一次。初创公司将是执行此操作的好时机。使其工作的代码行是:

DefaultComparisonConfigurator.ConfigureEqualityComparer<Type>(new HackedTypeEqualityComparer());
Run Code Online (Sandbox Code Playgroud)

执行该代码后,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(); }

}
Run Code Online (Sandbox Code Playgroud)

2.配置器

此类使用反射来配置 的基础字段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)); } }

    }
} 
Run Code Online (Sandbox Code Playgroud)

3.兼容的IComparer<T>实现

这基本上是一个装饰器,用于确保注入之间和注入时的IComparer<T>兼容性。它确保配置的实现认为相等的任何两个值始终具有 的比较结果。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); }

}
Run Code Online (Sandbox Code Playgroud)