TypeDelegator 相等性不一致?

发布于 2024-12-07 21:35:43 字数 1116 浏览 0 评论 0原文

考虑下面的代码:

    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

Consider the following code:

    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
       }
   }

How come the various versions of Equals return different results? The EqualityComparer.Default probably calls Object.Equals, so these results match, although inconsistent in themselves. And the normal instance version of Equals both return true.

This obviously creates problems when having a method return a Type that actually inherits from TypeDelegator. Imagine for example placing these types as keys in a dictionary, which by default use the EqualityComparer.Default for comparisons.

Is there any way to resolve this problem? I would like all the methods in the code above return true.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

挽容 2024-12-14 21:35:44

EqualityComparer 默认为 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;
            }
        }
    }

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

EqualityComparer<T> defauls to the object.Equals method, so the 1) and 2) cases are equivalent to 5) and 6).

I don't see why these comparison should be consistent by default. The true cases happen because System.Type equality implementation is based on the UnderlyingSystemType property. So, you could override Equals(object) and Equals(Type) - BTW, virtual only on Framework 4 -, but that wouldn't fix case 3).

So, what you can do to make sure it is consistent is this:

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

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

With this implementation, all cases will report false, which is consistent, but I'm unsure about the side effects... I suppose it depends on what your code does eventually.

記憶穿過時間隧道 2024-12-14 21:35:43

下面的代码返回一个 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;
} 

如果您查看程序集,它会执行 a:cmp rdx、rcx,因此只是直接内存比较。

您可以使用以下命令重现它:

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

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

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

The following code returns a System.RuntimeType

Type t1 = typeof(string);

If you look at the code for Type there is:

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

    return Equals(o as Type); 
}

BUT, System.RuntimeType has:

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

And if you view the assembly it executes a: cmp rdx, rcx, so just a direct memory compare.

You can reproduce it using the following:

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

So it looks like RuntimeType is overriding the Type Equals method to do a direct comparison... It would appear there is no easy way around the issue (without supplying a comparer).

EDITED TO ADD:
Out of curiosity, I had a look at the .NET 1.0 & 1.1 implementation of RuntimeType. They don't have the override of Equals in RuntimeType, so the issue was introduced in .NET 2.0.

半暖夏伤 2024-12-14 21:35:43

更新

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

很好地解释了为什么它会这样工作。我不相信有针对 Object.Equals 情况的解决方案。但是,

我找到了一种方法,通过使用反射配置默认相等比较器来解决 EqualityComparer.Default 情况下的问题。

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

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

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

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

1. 自定义 IEqualityComparer 实现

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

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.Default 的基础字段。作为奖励,此类还公开了一种操作 Comparer.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 实现

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

}

Update

The code from this answer has become a repository on GitHub: Undefault.NET on GitHub

Steven gives a good explanation of why this works the way it does. I do not believe there is a solution for the Object.Equals case. However,

I've found a way to fix the issue in the EqualityComparer<T>.Default case by configuring the default equality comparer with reflection.

This little hack only needs to happen once per application life cycle. Startup would be a good time to do this. The line of code that will makes it work is:

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

After that code has been executed, EqualityComparer<Type>.Default.Equals(t2, t1)) will yield the same result as EqualityComparer<Type>.Default.Equals(t1,t2)) (in your example).

The supporting infrastructure code includes:

1. a custom IEqualityComparer<Type> implementation

This class handles equality comparison the way that you want it to behave.

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. a Configurator class

This class uses reflection to configure the underlying field for EqualityComparer<T>.Default. As a bonus, this class exposes a mechanism to manipulate the value of Comparer<T>.Default as well, and ensures that the results of configured implementations are compatible. There is also a method to revert configurations back to the Framework defaults.

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. a compatible IComparer<T> implementation

This is basically a decorator for IComparer<T> that ensures compatibility between Comparer<T> and EqualityComparer<T> when EqualityComparer<T> is injected. It makes sure that any two values that the configured IEqualityComparer<T> implementation thinks are equal will always have a comparison result of 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); }

}
相对绾红妆 2024-12-14 21:35:43

迷人的q。

中间的 Equals 均为 true 是因为 Type.Equals 返回在 上调用的 ReferenceEquals 的值双方的 >UnderlyingSystemType 属性 - 和 TypeDelegator 覆盖 UnderlyingSystemType 以返回您构建它的 Type

我不知道如何说服非类型相等操作理解这一点。我怀疑你不能,并且你需要始终提供一个具有适当意识的EqualityComparer

Fascinating q.

The middle Equals both being true are because Type.Equals returns the value of ReferenceEquals as invoked on the UnderlyingSystemType property for both sides - and TypeDelegator overrides UnderlyingSystemType to return the Type you constructed it with!

How you can persuade a non-Type-ish equality operation to understand this, I don't know. I suspect you can't, and you'll need to always supplier a suitably aware EqualityComparer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文