C# TypeDelegator等式不一致?
考虑以下代码:C# TypeDelegator等式不一致?,c#,.net,types,equals,equality,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); T
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
}
}
类MyType:TypeDelegator
{
公共MyType(父类型)
:基(父)
{
}
}
班级计划
{
静态void Main(字符串[]参数)
{
类型t1=类型(字符串);
t2型=新的MyType(typeof(string));
Console.WriteLine(EqualityComparer.Default.Equals(t1,t2));//q
中间的Equals
都是true
是因为Type。Equals
返回ReferenceEquals
的值,该值在underyingsystemtype
属性中为双方调用,并且TypeDelegator
重写underyingsystemtype
以返回您构造的类型用它来做
我不知道如何说服非类型的
-ish相等操作来理解这一点。我怀疑你不能理解,你需要始终提供一个适当感知的相等比较程序
相等比较程序
对对象进行defauls。Equals方法,因此1)和2)情况相当于5)和6)
我不明白为什么默认情况下这些比较应该是一致的。真正的情况发生是因为System.Type
equality实现基于underlineSystemType
属性。因此,您可以覆盖Equals(object)和Equals(Type)-顺便说一句,仅在框架4上是虚拟的,但这无法修复情况3)
因此,您可以做些什么来确保其一致性:
class MyType : TypeDelegator
{
public MyType(Type parent)
: base(parent)
{
}
public override Type UnderlyingSystemType
{
get
{
return this;
}
}
}
在这个实现中,所有情况都将报告false,这是一致的,但我不确定副作用……我想这取决于您的代码最终执行的操作。以下代码返回System.RuntimeType
Type t1 = typeof(string);
如果查看类型的代码,则有:
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中引入了这个问题。Update
此答案中的代码已成为GitHub上的存储库:
Steven很好地解释了为什么它是这样工作的。我认为对于对象.Equals
情况没有解决方案。但是
我找到了一种方法来解决EqualityComparer.Default
案例中的问题,方法是配置带有反射的默认相等比较器。
此小技巧在每个应用程序生命周期中只需发生一次。启动是一个很好的时机。使其工作的代码行是:
DefaultComparisonConfigurator.ConfigureEqualityComparer<Type>(new HackedTypeEqualityComparer());
我只是出于兴趣把这个半评论半回答放在那里——我完全希望在大炮带来实际答案时删除它:)好吧,我理解你的观点,但是我看不出为什么默认的相等比较器应该根据其参数的顺序返回不同的结果。这感觉接近于我所说的错误在我看来,.NET框架(如果类型实现了IEquatable,可能会解决这个问题)。重写UnderlineingSystemType以返回CLR提供的运行时类型以外的任何内容似乎是个坏主意,可能会破坏一些代码。我认为它应该是一致的原因当然是如果没有其他不一致的地方令人困惑,并且使用一个根据参数顺序返回不同结果的等号是错误的st完全错误。我开始怀疑没有好的解决方案,因为System.Type
没有实现IEquatable。(为什么不呢?)如果它确实使用Object.Equals和默认的equality comparer,则都会使用正确的equality comparer。感谢您的分析。在对对象进行强制转换时,我没有意识到这个问题。如果您问我的话,框架中的类型和equality不太一致。我同意这不太正确,看起来确实像是一个bug/意外不道德的行为。
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(); }
}
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)); } }
}
}
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); }
}