Как я могу улучшить этот код: Наследование и 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).