【问题标题】:TypeDelegator equality inconsistency?TypeDelegator相等不一致?
【发布时间】:2011-10-03 09:34:55
【问题描述】:

考虑以下代码:

    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

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

有没有办法解决这个问题?我希望上面代码中的所有方法都返回true

【问题讨论】:

    标签: c# .net types equals equality


    【解决方案1】:

    以下代码返回一个 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 方法来进行直接比较...看起来没有简单的方法解决这个问题(不提供比较器)。

    编辑添加: 出于好奇,我查看了 RuntimeType 的 .NET 1.0 和 1.1 实现。它们在 RuntimeType 中没有 Equals 的覆盖,所以这个问题是在 .NET 2.0 中引入的。

    【讨论】:

    • 感谢您的分析。投射到对象时我没有意识到这个问题。如果你问我,框架中的类型和相等性不太一致。
    • 我同意这不太正确,而且看起来确实是错误/意外行为。
    【解决方案2】:

    更新

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

    Steven 很好地解释了为什么它会这样工作。我不相信Object.Equals 案例有解决方案。不过,

    我找到了一种方法来解决EqualityComparer&lt;T&gt;.Default 案例中的问题,方法是使用反射配置默认相等比较器。

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

    DefaultComparisonConfigurator.ConfigureEqualityComparer<Type>(new HackedTypeEqualityComparer());
    

    执行该代码后,EqualityComparer&lt;Type&gt;.Default.Equals(t2, t1)) 将产生与EqualityComparer&lt;Type&gt;.Default.Equals(t1,t2)) 相同的结果(在您的示例中)。

    支持的基础架构代码包括:

    1。自定义IEqualityComparer&lt;Type&gt; 实现

    该类按照您希望的方式处理相等比较。

    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&lt;T&gt;.Default 的基础字段。作为奖励,这个类还公开了一种机制来操纵Comparer&lt;T&gt;.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&lt;T&gt; 实现

    这基本上是IComparer&lt;T&gt; 的装饰器,当EqualityComparer&lt;T&gt; 被注入时,它确保Comparer&lt;T&gt;EqualityComparer&lt;T&gt; 之间的兼容性。它确保配置的IEqualityComparer&lt;T&gt; 实现认为相等的任何两个值将始终具有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); }
    
    }
    

    【讨论】:

      【解决方案3】:

      迷人的q。

      中间的Equals 都是true 是因为Type.Equals 返回ReferenceEquals 的值,正如在双方的UnderlyingSystemType 属性上调用的那样 - 并且TypeDelegator 覆盖UnderlyingSystemType 以返回@987654328 @你用它构建的!

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

      【讨论】:

      • 我只是把这个半评论半答案放在那里是为了感兴趣 - 我完全希望一旦大枪出现实际答案就会删除它:)
      • 嗯,我理解你的意思,但我看不出为什么默认相等比较器应该根据其参数的顺序返回不同的结果。在我看来,这感觉接近于 .NET 框架中的一个错误。 (如果 Type 实现了 IEquatable,这可能已经解决了)。
      【解决方案4】:

      EqualityComparer&lt;T&gt; 默认为 object.Equals 方法,因此 1) 和 2) 情况等价于 5) 和 6)。

      我不明白为什么这些比较默认应该是一致的。真正的情况发生是因为System.Type 相等实现基于UnderlyingSystemType 属性。因此,您可以覆盖 Equals(object) 和 Equals(Type) - 顺便说一句,仅在 Framework 4 上是虚拟的 - 但这不能解决案例 3)。

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

       class MyType : TypeDelegator
          {
             public MyType(Type parent)
                : base(parent)
             {
             }
      
              public override Type UnderlyingSystemType
              {
                  get
                  {
                      return this;
                  }
              }
          }
      

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

      【讨论】:

      • 重写 UnderlyingSystemType 以返回除 CLR 提供的运行时类型以外的任何内容似乎是个坏主意,并且可能会破坏某些代码。我认为它当然应该保持一致的原因是,如果没有其他不一致会令人困惑,并且有一个 Equals 根据参数的顺序返回不同的结果是完全错误的。我开始怀疑没有好的解决方案,因为System.Type 没有实现 IEquatable。 (为什么不呢?)如果是 Object.Equals 和默认相等比较器都将使用正确的相等比较器。
      猜你喜欢
      • 2011-11-10
      • 1970-01-01
      • 1970-01-01
      • 2018-03-04
      • 1970-01-01
      • 2019-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多