Как я могу улучшить этот код: Наследование и IEquatable <>

Это пример того, что я пытаюсь сделать:

public class Foo : IEquatable<Foo>
{
    public bool Equals(Foo other)
    {
        Type type1 = this.GetType();
        Type type2 = other.GetType();

        if (type1 != type2)
            return false;

        if (type1 == typeof(A))
        {
            A a = (A)this;
            A b = (A)other;

            return a.Equals(b);
        }
        else if (type1 == typeof(B))
        {
            B c = (B)this;
            B d = (B)other;

            return c.Equals(d);
        }
        else
        {
            throw new Exception("Something is wrong");
        }
    }
}

public class A : Foo, IEquatable<A>
{
    public int Number1 { get; set; }
    public int Number2 { get; set; }

    public bool Equals(A other)
    {
        return this.Number1 == other.Number1 && this.Number2 == other.Number2;
    }
}

public class B : Foo, IEquatable<B>
{
    public int Number1 { get; set; }
    public int Number2 { get; set; }
    public int Number3 { get; set; }

    public bool Equals(B other)
    {
        return this.Number1 == other.Number1 && this.Number2 == other.Number2 && this.Number3 == other.Number3;
    }
}

Но, как вы можете видеть выше, мне пришлось бы использовать множество условных выражений "if" для идентификации реального типа. Проблема в том, что я должен использовать базовый класс. Например:

A a = new A();
Foo foo = a;

foo.Equals(another);

Ответ 1

Как прямой ответ на ваш вопрос, вы, кажется, реализуете IEquatable<Foo>, всегда откладывая реализацию (конкретного) подкласса IEquatable<self>. Это будет выглядеть примерно так:

(Плохой код, только для демонстрации)

// You need to specify what you want when this method is called on a 
// vanilla Foo object. I assume here that Foo is abstract. If not, please
// specify desired behaviour.
public bool Equals(Foo other)
{
    if (other == null || other.GetType() != GetType())
        return false;

    // You can cache this MethodInfo..
    var equalsMethod = typeof(IEquatable<>).MakeGenericType(GetType())
                                           .GetMethod("Equals");

    return (bool)equalsMethod.Invoke(this, new object[] { other });
}

Но на самом деле неясно, зачем нужны сравнения сравнений, чтобы всегда идти "через" реализацию базового класса IEquatable<self>.

У рамки уже есть виртуальный метод Equals, который приведет к отправке вызовов равенства соответствующему методу. Кроме того, EqualityComparar<T>.Default (который используется большинством типов коллекций для выполнения проверок равенства) уже имеет умение выбрать IEquatable<self>.Equals(self) или object.Equals(object), если это необходимо.

Попытка создать реализацию равенства в базовом классе, который просто перенаправляет запрос, не добавляет никакого значения ни к чему, насколько я могу видеть.

Без дальнейшего объяснения того, зачем вам нужна реализация базового класса IEquatable<>, я рекомендую правильно применять равенство для каждого типа. Например:

public class A : Foo, IEquatable<A>
{
    public int Number1 { get; set; }
    public int Number2 { get; set; }

    public bool Equals(A other)
    {
        return other != null 
            && Number1 == other.Number1
            && Number2 == other.Number2;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as A);
    }

    public override int GetHashCode()
    {
        return Number1 ^ Number2;
    }
}

Ответ 2

Попробуйте этот фрагмент кода:

public class Foo : IEquatable<Foo>
{
    public virtual bool Equals(Foo other)
    {
        return true;
    }
}

public class A : Foo,IEquatable<A>
{
    public int Number1 { get; set; }
    public int Number2 { get; set; }

    public override bool Equals(Foo other)
    {
        if (other.GetType() == typeof(A))
        {
            return Equals((A)other);                
        }
        throw new InvalidOperationException("Object is not of type A");
    }
    public bool Equals(A other)
    {
        return this.Number1 == other.Number1 && this.Number2 == other.Number2;
    }
}

public class B : Foo,IEquatable<B>
{
    public int Number1 { get; set; }
    public int Number2 { get; set; }
    public int Number3 { get; set; }

    public override bool Equals(Foo other)
    {
        if (other.GetType() == typeof(B))
        {
            return Equals((B)other);

        }
        throw new InvalidOperationException("Object is not of type B");
    }
    public bool Equals(B other)
    {
        return this.Number1 == other.Number1 && this.Number2 == other.Number2 && this.Number3 == other.Number3;
    }
}

Примечание. Функцию Assert можно использовать для проверки типов.

Ответ 3

Один из вариантов - переместить свойства Number1 и Number2 в базовый класс и сравнить только элемент, добавленный в подкласс в методах равенства подклассов.

class Foo
{
    // move the common properties to the base class
    public int Number1 { get; set; }
    public int Number2 { get; set; }

    public override bool Equals(object obj)
    {
        Foo objfoo = obj as Foo;
        return 
            objfoo != null
            // require objects being compared to be of
            // the same derived type (optionally)
            && this.GetType() == obj.GetType()
            && objfoo.Number1 == this.Number1
            && objfoo.Number2 == this.Number2;
    }
    public override int GetHashCode()
    {
        // xor the hash codes of the elements used to evaluate
        // equality
        return Number1.GetHashCode() ^ Number2.GetHashCode();
    }
}

class A : Foo, IEquatable<A>
{
    // A has no properties Foo does not.  Simply implement
    // IEquatable<A>

    public bool Equals(A other)
    {
        return this.Equals(other);
    }

    // can optionally override Equals(object) and GetHashCode()
    // to call base methods here
}

class B : Foo, IEquatable<B>
{
    // Add property Number3 to B
    public int Number3 { get; set; }
    public bool Equals(B other)
    {
        // base.Equals(other) evaluates Number1 and Number2
        return base.Equals(other)
            && this.Number3 == other.Number3;
    }
    public override int GetHashCode()
    {
        // include Number3 in the hashcode, since it is used
        // to evaluate equality
        return base.GetHashCode() ^ Number3.GetHashCode();
    }
    public override bool Equals(object obj)
    {
        return this.Equals(obj as B);
    }
}

Ответ 4

Я думаю, что производные классы не должны обрабатываться в базовых классах. Обычно "Foo" ничего не знает об A и B.

По-прежнему можно сделать виртуальную реальную реализацию IEquatable, позволяя A и B переопределять ее и выполнять свои конкретные проверки на равенство, даже если и проверка равенства, и проверочный экземпляр доступны только как "Foo" или "Object".

Это будет обрабатывать .Equals(Foo obj) как более конкретную форму Object.Equals(Object obj).