TypeDelegator 相等性不一致?
考虑下面的代码:
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
。
当方法返回实际上继承自 TypeDelegator
的 Type
时,这显然会产生问题。想象一下,例如将这些类型作为键放入字典中,默认情况下使用 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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
EqualityComparer
默认为 object.Equals 方法,因此 1) 和 2) 情况相当于 5) 和 6)。我不明白为什么默认情况下这些比较应该是一致的。真实情况的发生是因为
System.Type
相等实现基于UnderlyingSystemType
属性。因此,您可以重写 Equals(object) 和 Equals(Type) - 顺便说一句,仅在 Framework 4 上是虚拟的 - 但这并不能解决情况 3)。所以,你可以做的是确保它是一致的:
通过这个实现,所有情况都会报告 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 theUnderlyingSystemType
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:
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.
下面的代码返回一个 System.RuntimeType
如果您查看 Type 的代码,则有:
但是,System.RuntimeType 有:
如果您查看程序集,它会执行 a:cmp rdx、rcx,因此只是直接内存比较。
您可以使用以下命令重现它:
所以看起来 RuntimeType 正在重写 Type Equals 方法来进行直接比较...看来没有简单的方法来解决这个问题(不提供比较器)。
编辑添加:
出于好奇,我查看了 .NET 1.0 和 .NET 1.0。 1.1 RuntimeType的实现。它们没有覆盖 RuntimeType 中的 Equals,因此该问题是在 .NET 2.0 中引入的。
The following code returns a System.RuntimeType
If you look at the code for Type there is:
BUT, System.RuntimeType has:
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:
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.
更新
此答案中的代码已成为 GitHub 上的存储库:GitHub 上的 Undefault.NET
很好地解释了为什么它会这样工作。我不相信有针对
Object.Equals
情况的解决方案。但是,我找到了一种方法,通过使用反射配置默认相等比较器来解决
EqualityComparer.Default
情况下的问题。这个小技巧在每个应用程序生命周期只需要发生一次。初创公司将是执行此操作的好时机。使其工作的代码行是:
执行该代码后,
EqualityComparer.Default.Equals(t2, t1))
将产生与EqualityComparer相同的结果。 Type>.Default.Equals(t1,t2))
(在您的示例中)。支持的基础结构代码包括:
1. 自定义
IEqualityComparer
实现该类按照您希望的方式处理相等比较。
2. Configurator 类
该类使用反射来配置.Default 值的机制,并确保配置的实现的结果是兼容的。还有一种方法可以将配置恢复为框架默认值。
EqualityComparer.Default
的基础字段。作为奖励,此类还公开了一种操作 Comparer3. 兼容的
IComparer
实现这基本上是当注入
IComparer
的装饰器,确保Comparer
和IComparer
之间的兼容性code>EqualityComparerEqualityComparer
时。它确保配置的IEqualityComparer
实现认为相等的任何两个值始终具有0
的比较结果。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:
After that code has been executed,
EqualityComparer<Type>.Default.Equals(t2, t1))
will yield the same result asEqualityComparer<Type>.Default.Equals(t1,t2))
(in your example).The supporting infrastructure code includes:
1. a custom
IEqualityComparer<Type>
implementationThis class handles equality comparison the way that you want it to behave.
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 ofComparer<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.3. a compatible
IComparer<T>
implementationThis is basically a decorator for
IComparer<T>
that ensures compatibility betweenComparer<T>
andEqualityComparer<T>
whenEqualityComparer<T>
is injected. It makes sure that any two values that the configuredIEqualityComparer<T>
implementation thinks are equal will always have a comparison result of0
.迷人的q。
中间的
Equals
均为true
是因为Type.Equals
返回在上调用的
属性 - 和ReferenceEquals
的值双方的 >UnderlyingSystemTypeTypeDelegator
覆盖UnderlyingSystemType
以返回您构建它的Type
!我不知道如何说服非类型相等操作理解这一点。我怀疑你不能,并且你需要始终提供一个具有适当意识的
EqualityComparer
。Fascinating q.
The middle
Equals
both beingtrue
are becauseType.Equals
returns the value ofReferenceEquals
as invoked on theUnderlyingSystemType
property for both sides - andTypeDelegator
overridesUnderlyingSystemType
to return theType
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 awareEqualityComparer
.