请考虑以下代码:
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
。
答案 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))
相同的结果(在您的示例中)。
支持基础设施代码包括:
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(); }
}
此类使用反射来配置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)); } }
}
}
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;
}
}
}
通过这个实现,所有情况都会报告错误,这是一致的,但我不确定副作用......我想这取决于你的代码最终会做什么。