TypeDelegator等式不一致?

时间:2011-10-03 09:34:55

标签: c# .net types equals equality

请考虑以下代码:

    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的{​​{1}}时,这显然会产生问题。想象一下,例如将这些类型作为键放在字典中,默认情况下使用EqualityComparer.Default进行比较。

有什么方法可以解决这个问题吗?我希望上面代码中的所有方法都返回TypeDelegator

4 个答案:

答案 0 :(得分:7)

以下代码返回System.RuntimeType

Type t1 = typeof(string);

如果你看一下Type的代码,那就是:

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

    return Equals(o as Type); 
}

但是,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;
} 

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

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

bool a = t1.Equals((object)t2); // False
bool b = t1.Equals(t2); // True

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

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

答案 1 :(得分:4)

更新

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

史蒂文很好地解释了为什么它的运作方式。我不相信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。 配置器

此类使用反射来配置EqualityComparer<T>.Default的基础字段。作为奖励,此类公开了一种机制来操纵Comparer<T>.Default的值,并确保配置的实现的结果是兼容的。还有一种方法可以将配置恢复为Framework默认值。

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>的装饰器,可确保在注入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); }

}

答案 2 :(得分:2)

引人入胜q。

中间Equals都是true,因为Type.Equals会返回ReferenceEquals属性为双方调用的UnderlyingSystemType的值 - 而{TypeDelegator 1}}覆盖UnderlyingSystemType以返回您构建它的Type

我不知道如何说服非Type - ish平等操作来理解这一点。我怀疑你不能,而且你需要始终提供适当的EqualityComparer

答案 3 :(得分:2)

EqualityComparer<T>默认为object.Equals方法,因此1)和2)情况相当于5)和6)。

我不明白为什么默认情况下这些比较应该是一致的。发生真实的情况是因为System.Type等式实现基于UnderlyingSystemType属性。因此,您可以覆盖Equals(对象)和Equals(Type) - BTW,仅在框架4上进行虚拟 - 但不会修复案例3。)

所以,你可以做些什么来确保它是一致的:

 class MyType : TypeDelegator
    {
       public MyType(Type parent)
          : base(parent)
       {
       }

        public override Type UnderlyingSystemType
        {
            get
            {
                return this;
            }
        }
    }

通过这个实现,所有情况都会报告错误,这是一致的,但我不确定副作用......我想这取决于你的代码最终会做什么。