Подтвердить что ты не робот

Непоследовательность равенства типаDelegator?

Рассмотрим следующий код:

    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, который наследует от TypeDelegator. Представьте, например, размещение этих типов в качестве ключей в словаре, которые по умолчанию используют EqualityComparer.Default для сравнения.

Есть ли способ решить эту проблему? Я хотел бы, чтобы все методы в приведенном выше коде вернули true.

4b9b3361

Ответ 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. У них нет переопределения Equals в RuntimeType, поэтому проблема была внедрена в .NET 2.0.

Ответ 2

Update

Код из этого ответа стал репозиторием GitHub: Undefault.NET на GitHub

Стивен дает хорошее объяснение, почему это работает так, как оно. Я не думаю, что есть решение для случая Object.Equals. Однако

Я нашел способ исправить проблему в случае EqualityComparer<T>.Default, настроив сопоставитель равенства по умолчанию с отражением.

Этот небольшой взлом должен произойти только один раз в течение жизненного цикла приложения. Запуск будет хорошим временем для этого. Строка кода, которая заставит ее работать, следующая:

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

После того, как этот код был выполнен, EqualityComparer<Type>.Default.Equals(t2, t1)) даст тот же результат, что и EqualityComparer<Type>.Default.Equals(t1,t2)) (в вашем примере).

Код поддерживающей инфраструктуры включает в себя:

1. пользовательская реализация 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(); }

}

2. класс Configurator

Этот класс использует отражение для настройки базового поля для 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)); } }

    }
} 

3. совместимая реализация 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); }

}

Ответ 3

Увлекательный q.

Средние Equals оба являются true, потому что Type.Equals возвращает значение ReferenceEquals как вызванное в свойстве UnderlyingSystemType для обеих сторон - и TypeDelegator переопределяет UnderlyingSystemType, чтобы вернуть Type вы построили его с помощью

Как вы можете убедить в том, что я не знаю. Я подозреваю, что вы не можете, и вам всегда нужно знать подходящий EqualityComparer.

Ответ 4

EqualityComparer<T> defauls to object.Equals метод, поэтому 1) и 2) случаи эквивалентны 5) и 6).

Я не понимаю, почему это сравнение должно быть последовательным по умолчанию. Истинные случаи случаются, потому что реализация равенства System.Type основана на свойстве UnderlyingSystemType. Таким образом, вы можете переопределить Equals (object) и Equals (Type) - BTW, виртуальные только на Framework 4 -, но это не исправило бы случай 3).

Итак, что вы можете сделать, чтобы убедиться, что оно последовательное:

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

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

С этой реализацией все случаи сообщают об ошибке, что согласуется, но я не уверен в побочных эффектах... Я полагаю, это зависит от того, что ваш код в конечном итоге.