Ответ 1
Явная ошибка, нет. Хорошая идея, возможно, или нет.
Что значит одно, чтобы быть равным другому? Мы могли бы стать довольно философскими, если бы действительно хотели.
Будучи лишь слегка философским, есть несколько вещей, которые должны иметь место:
- Равенство рефлексивно: Идентичность влечет за собой равенство.
x.Equals(x)
должен выполняться. - Равенство симметрично. Если
x.Equals(y)
, тоy.Equals(x)
, а если!x.Equals(y)
, то!y.Equals(x)
. - Равенство транзитивно. Если
x.Equals(y)
иy.Equals(z)
, тоx.Equals(z)
.
Есть несколько других, хотя только они могут быть непосредственно отражены только кодом Equals()
.
Если реализация переопределения object.Equals(object)
, IEquatable<T>.Equals(T)
, IEqualityComparer.Equals(object, object)
, IEqualityComparer<T>.Equals(T, T)
, ==
или !=
не соответствует вышеизложенному, это явная ошибка.
Другим методом, который отражает равенство в .NET, является object.GetHashCode()
, IEqualityComparer.GetHashCode(object)
и IEqualityComparer<T>.GetHashCode(T)
. Здесь есть простое правило:
Если a.Equals(b)
, то он должен содержать a.GetHashCode() == b.GetHashCode()
. Эквивалент имеет место для IEqualityComparer
и IEqualityComparer<T>
.
Если это не выполняется, то снова у нас есть ошибка.
Помимо этого, нет никаких общих правил, которые должны означать равенство. Это зависит от семантики класса, предоставляемой его собственными переопределениями Equals()
или теми, которые налагаются на него сравнением равенства. Разумеется, эти семантики должны быть явно очевидными или документированными в классе или сравнителю.
В целом, как Equals
и/или a GetHashCode
имеют ошибку:
- Если он не может обеспечить рефлексивные, симметричные и транзитивные свойства, описанные выше.
- Если отношение между
GetHashCode
иEquals
не указано выше. - Если это не соответствует его документированной семантике.
- Если он выбрасывает неуместное исключение.
- Если он переходит в бесконечный цикл.
- На практике, если потребуется так много времени, чтобы вернуться, чтобы калечить вещи, хотя можно было бы утверждать, что здесь существует теория против практики.
С переопределениями на Attribute
, equals имеет рефлексивные, симметричные и транзитивные свойства, он GetHashCode
соответствует ему, а документация для него Equals
переопределяет:
Этот API поддерживает инфраструктуру .NET Framework и не предназначен для использования непосредственно из вашего кода.
Вы не можете сказать, что ваш пример опровергает это!
Поскольку код, на который вы жалуетесь, не прерывается ни в одном из этих пунктов, это не ошибка.
В этом коде есть ошибка:
var attributes = typeof(Bar).GetCustomAttributes(true).OfType<FooAttribute>().ToList<FooAttribute>();
var getC = attributes.First(item => item.Name == "C");
attributes.Remove(getC);
Сначала вы запрашиваете элемент, который соответствует критериям, а затем запрашиваете тот, который равен ему, который нужно удалить. Нет никакой причины, не рассматривая семантику равенства для рассматриваемого типа, чтобы ожидать, что getC
будет удален.
Что вам нужно сделать:
bool calledAlready;
attributes.RemoveAll(item => {
if(!calledAlready && item.Name == "C")
{
return calledAlready = true;
}
});
То есть мы используем предикат, который соответствует первому атрибуту с Name == "C"
и никаким другим.