Переопределение равно в реализации интерфейса C#
У меня есть класс, который реализует интерфейс, такой как этот:
interface IInterface
{
string PropA { get; }
string PropB { get; }
}
class AClass : IInterface
{
string PropA { get; protected set; }
string PropB { get; protected set; }
}
Равенство определяется на основе PropA и PropB. При переопределении метода Equals для AClass я должен попытаться привести obj к AClass, например так:
public override bool Equals(object obj)
{
AClass other = obj as AClass;
return other != null
&& AClass.PropA == other.PropA
&& AClass.PropB == PropB;
}
Или я должен попытаться привести obj к IInterface, например так:
public override bool Equals(object obj)
{
IInterface other = obj as IInterface;
return other != null
&& AClass.PropA == other.PropA
&& AClass.PropB == PropB;
}
3 ответа
Вы можете делать все, что захотите. Эти два функционально не одинаковы, но что "правильно" для вас, мы не можем ответить. Если у меня есть BClass
класс, который реализует тот же интерфейс, и имеет одинаковые значения для обоих свойств, если он будет равен вашему AClass
объект? Если да, сделайте последнее, если нет, сделайте первое.
Лично я бы нашел последнее относительно. Обычно я считаю, что если класс собирается реализовать свое собственное определение равенства, другие классы не должны быть равны ему. Одна из основных причин заключается в том, что предпочтительно, если равенство симметрично. Так сказать aclass.Equals(bclass)
должен вернуть то же самое, что и bclass.Equals(aclass)
, Получить такое поведение, когда вы не ограничиваете равенство одним и тем же типом,... сложно. Требуется сотрудничество всех смежных классов.
Если у вас есть веская причина для сравнения IInterface
реализации, в которых они могут быть различными базовыми классами, но все же оба "равны", я лично предпочел бы создать IEqualityComparer<IInterface>
это определяет равенство для этого интерфейса. Это будет отдельным от определения равенства для любого из двух реализующих классов.
Решите, как вам это нужно для функционирования.
Решарперская реализация:
class AClass : IInterface, IEquatable<AClass>
{
public bool Equals(AClass other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(this.PropA, other.PropA) && string.Equals(this.PropB, other.PropB);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof (AClass)) return false;
return Equals((AClass)obj);
}
public override int GetHashCode()
{
unchecked
{
return ((this.PropA != null ? this.PropA.GetHashCode() : 0) * 397) ^ (this.PropB != null ? this.PropB.GetHashCode() : 0);
}
}
public string PropA { get; protected set; }
public string PropB { get; protected set; }
}
Если цель интерфейса состоит в том, чтобы скрыть от потребителей тот факт, что два эквивалентных объекта могут принадлежать к разным классам, может быть хорошей идеей определить структуру, которая содержит одно частное поле этого типа интерфейса и связывает его с соответствующими методами интерфейсы. Использование такой структуры, как правило, должно быть практически таким же эффективным, как и использование переменной типа интерфейса (главное исключение будет, если структура окажется в штучной упаковке), но оно не позволит клиентскому коду увидеть фактические вещи, реализующие интерфейс.
Например, один может иметь интерфейсы IReadableMatrix<T>
а также IImmutableMatrix<T>
и соответствующие структуры ReadableMatrix<T>
а также ImmutableMatrix<T>
с членами только для чтения int Height
, int Width
, а также T this[int row, int column]
, а также ImmutableMatrix<T> AsImmutable();
, Код, который использует ImmutableMatrix<double>
не должно волновать, как это сохранено; вполне возможно, что два случая ImmutableMatrix
может содержать ссылки на различные реализации IImmutableMatrix<T>
которые сообщают идентичный контент в каждой ячейке, но хранят вещи совершенно по-разному. Один может быть примером ArrayBackedMatrix<double>
, который содержит массив 12x12, который содержит нули в каждом элементе, кроме элементов на диагонали, в то время как другой может быть DiagonalMatrix<double>
и использовать массив из 12 элементов, который хранит вещи только по диагонали (и возвращает ноль в ответ на запрос любого другого элемента). Использование разных типов для хранения данных массива должно быть деталью реализации и не раскрываться клиенту.
Одна небольшая деталь использования структуры для обертывания массивов состоит в том, что поля ссылочного типа структуры значения по умолчанию будут нулевыми, а сама структура - нет. Таким образом, может быть желательно, чтобы структура реализовала IsNull
свойство, которое возвращает true
если поле поддержки null
или же другие члены структуры должны проверить, является ли вспомогательное поле пустым и, если это так, вести себя как пустая матрица 0x0.