Как сравнить два объекта на основе их базового класса? - PullRequest
1 голос
/ 02 ноября 2009

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

Теперь я могу исправить код, сделав BaseClass неабстрактным, а затем вернуть new BaseClass объект в ToBassClass(). Но разве нет более элегантного и эффективного решения?

abstract class BaseClass
{
   BaseClass(int x)
   {
       X = x;
   }

   int X { get; private set; }

   // It is probably not necessary to override Equals for such a simple class,
   // but I've done it to illustrate my point.
   override Equals(object other)
   {
       if (!other is BaseClass)
       {
           return false;
       }

       BaseClass otherBaseClass = (BaseClass)other;

       return (otherBaseClass.X == this.X);
   }

   BaseClass ToBaseClass()
   {
       // The explicit is only included for clarity.
       return (BaseClass)this;
   }
}

class ClassA : BaseClass
{
   ClassA(int x, int y)
       : base (x)
   {
       Y = y;
   }

   int Y { get; private set; }
}

class ClassB : BaseClass
{
   ClassB(int x, int z)
       : base (x)
   {
       Z = z;
   }

   int Z { get; private set; }
}

var a = new A(1, 2);
var b = new B(1, 3);

// This fails because despite the call to ToBaseClass(), a and b are treated
// as ClassA and ClassB classes so the overridden Equals() is never called.
Assert.AreEqual(a.ToBaseClass(), b.ToBaseClass());

Ответы [ 4 ]

2 голосов
/ 02 ноября 2009

Это зависит от , где точно вы хотите проверить на равенство. Ясно, что экземпляры ClassA и ClassB никогда не будут "равны" в реальном смысле этого слова, поэтому переопределение поведения Equals так может привести к некоторым странным ошибкам в вашем коде.

Но если вы хотите сравнить их на основе определенных критериев , то вы можете реализовать определенный IEqualityComparer (или несколько сравнителей), который соответствует вашим потребностям.

Итак, в этом случае вы бы получили:

/// <Summary>
/// Compares two classes based only on the value of their X property.
/// </Summary>
public class ComparerByX : IEqualityComparer<BaseClass>
{
     #region IEqualityComparer<BaseClass> Members

     public bool Equals(BaseClass a, BaseClass b)
     {
         return (a.X == b.X);
     }

     public int GetHashCode(BaseClass obj)
     {
         return obj.X.GetHashCode();
     }

     #endregion

}

[Изменить] По поводу комментария:

Обратите внимание, что это не имеет ничего общего с переопределением метода Equals.

Но вы сможете проверить равенство следующим образом:

IEqualityComparer<BaseClass> comparer = new ComparerByX();
Assert.True(comparer.Equals(a, b));

Поначалу это может показаться не очень хорошим, но дает вам несколько преимуществ:

a) Вы можете иметь столько реализаций IEqualityComparer<T>, сколько захотите. В зависимости от ситуации может оказаться, что переопределение Equals не так уж и велико. Тогда вы рискуете сломать весь свой код в зависимости от этого.

b) На самом деле существует множество классов, которые используют IEqualityComparer<T> для сравнения предметов.

Например, вы можете использовать BaseClass в качестве ключа в словаре. В этом случае вы бы использовали перегрузку конструктора Dictionary<Key,Value>, которая принимает IEqualityComparer<T>:

Dictionary<BaseClass, SomeOtherClass> dictionary 
    = new Dictionary<BaseClass, SomeOtherClass>(new ComparerByX());

Таким образом, словарь будет использовать пользовательский ComparerByX во время поиска ключа.

Также, например, если вы используете LINQ, вы можете проверить пример метода Distinct () . Он также поддерживает перегрузку, которая возвращает различные значения, но сравнивается с использованием указанного пользовательского IEqualityComparer.

2 голосов
/ 02 ноября 2009

Во-первых, ваш код не компилируется. Во-вторых, когда ваш код исправлен так, что он компилируется (в частности, Assert.True изменяется на Assert.AreEqual), я вижу ожидаемые вами результаты. И это хорошо, так как это правильное поведение. Но вы не можете полагаться на то, что наследники не переопределяют Object.Equals, поэтому, если вы хотите, чтобы сравнение проходило только по базовому классу, вам следует реализовать IEqualityComparer<BaseClass>.

Вот версия вашего кода в том виде, в котором вы, вероятно, намеревались его скомпилировать:

abstract class BaseClass {
    public BaseClass(int x) { X = x; }

    public int X { get; private set; }

    public override bool  Equals(object other) {
        if (!(other is BaseClass)) {
            return false; 
        }

        BaseClass otherBaseClass = (BaseClass)other;
        return (otherBaseClass.X == this.X);
    }

    public BaseClass ToBaseClass() {
        return (BaseClass)this;
    }
}

class ClassA : BaseClass {
    public ClassA(int x, int y) : base (x) {
        Y = y;
    }

    public int Y { get; private set; }
}

class ClassB : BaseClass {
    public ClassB(int x, int z) : base (x) {
        Z = z;
    }

    public int Z { get; private set; }
}

class Program {
    static void Main(string[] args) {
        var a = new ClassA(1, 2);
        var b = new ClassB(1, 3);
        Assert.AreEqual(a.ToBaseClass(), b.ToBaseClass());
    }
}
2 голосов
/ 02 ноября 2009

Ну, как отметил Фрид, немного странно использовать Assert.True здесь - вы имели в виду Assert.AreEqual? Если это так, я бы ожидал, что это сработает (даже без вызова ToBaseClass), хотя это будет зависеть от среды тестирования.

Равенство сложно, когда дело доходит до наследования. Лично я бы создал соответствующий IEqualityComparer<BaseClass>, который явно говорит: «Я собираюсь протестировать этот конкретный аспект объекта» - что означает, что наследование в основном не включается.

1 голос
/ 21 декабря 2016

Я настоятельно рекомендую использовать KellermanSoftware.CompareNetObjects (хотя я не автор - он очень гибкий, эффективный и пока без ошибок!).

Я сделал то же самое, чтобы сравнить 2 объекта на основе базового класса.

1) Создайте «BaseClassComparer», который принимает тип, который вы хотите использовать для сравнения:

using System;
using KellermanSoftware.CompareNetObjects;

namespace Compare
{

    /// <summary>
    /// This allows us to compare objects based on a particular class (ie so we can compare on base classes)
    /// </summary>
    public class BaseClassComparer : KellermanSoftware.CompareNetObjects.TypeComparers.ClassComparer
    {


        private readonly Type _compareType;
        internal BaseClassComparer(Type compareType, RootComparer rootComparer) : base(rootComparer)
        {

            _compareType = compareType;
        }

        public override void CompareType(CompareParms parms)
        {
            parms.Object1Type = _compareType;
            parms.Object2Type = _compareType;

            base.CompareType(parms);
        }

        public override bool IsTypeMatch(Type type1, Type type2)
        {
            if (((_compareType.IsAssignableFrom(type1)) && (_compareType.IsAssignableFrom(type2)))) {
                return true;
            } else {
                return false;
            }
        }
    }
}

Затем добавьте этот класс в компаратор Kellerman:

_compare = New CompareLogic
_compare.Config.CustomComparers.Add(New BaseClassComparer(compareType, RootComparerFactory.GetRootComparer()))

И сравните прочь. Только различия между базовым типом (compareType) будут проверены / сообщены ...

...