Разбирая тему обобщений (по Шиелду 4.0), потребовалось написать обобщённый метод, который вернет логическое значение true, если в массиве содержится некоторое значение. Далее в книге поясняется, что так как T - обобщённый тип, то для сравнения объектов обобщённого типа необходимо что бы класс этих объектов реализовывал интерфейс IEquatable с параметром T или IEquatable. Написано так же, что в данных интерфейсах определен метод для сравнения Equals(). Отсюда сразу же несколько вопросов:
Во-первых, почему для сравнения объектов обобщённого типа нам вообще необходимо реализовывать какие-либо интерфейсы? Метод Equals() относится к методам определённым в Object => есть в каждом объекте. Я понял что для сравнения объектов в данном примере нужно реализовать описанные выше интерфейсы, но я не понял, ПОЧЕМУ это нужно сделать.
Во-вторых, по Шиелду, как я написал выше, для сравнения объектов в обобщённом методе предлагается реализовать Equtable с параметром T или IEqutable. Один параметризирован, другой нет. Вопрос - какой в каком случае необходимо использовать?
Далее Шиелд в своей книге демонстрирует пример метода, который проверяет, находится ли в массиве некоторое значение. Вот этот метод:
public static bool IsIn<T>(T what, T[] obs) where T : IEquatable<T>
{
foreach(T v in obs)
{
if (v.Equals(what))
{
return true;
}
else
{
return false;
}
}
}
Тут так же есть два вопроса.
Во-первых, начну с того, что данный метод не работает. Студия выдает ошибку компиляции "Не все ветви метода возвращают значение". Я несколько раз перепроверял написанный код, но ошибка остается. Как это исправить?
Во-вторых, я не уверен, правильно ли я понял, что сигнатура данного метода подразумевает, что в данный метод могут быть переданы только объекты тех типов, которые реализовывают интерфейс IEquatable? Другими словами, если я захочу передать в метод массив каких нибудь студентов, и найти в нём Васю Пупкина, то мой класс студента должен выглядить следующим образом?
class Student<T> : IEquatable<T>
{
public string Name;
public string Surname;
public bool Equals(T other)
{
//some code for compare
return false;
}
}
Прошу последовательно ответить на все вопросы. Спасибо
Equals(object obj)
появился в .NET в самом начале, а тогда еще не было дженериков. До введения дженериков в .NET 2.0, переопределение метода Equals(object obj)
в большинстве случаев выглядело приблизительно так:
public bool override Equals(object obj)
{
if (obj == null)
return false;
if (obj is MyClass)
{
MyClass other = (MyClass)obj;
// сравниваем this и other
}
return false;
}
Получается, в большинстве случаев, объекты разных типов не могут быть равны. Что бы упростить жизнь, придумали интерфейс IEquatable<T>
, в котором можно сразу сравнивать 2 объекта, без проверок их типов (что тоже занимает некоторое время).
По-хорошему теперь вы должны реализовать IEquatable<T>
для всех классов, которые можно сравнивать. При этом вы должны не забывать про переопределение старого Equals
, для совместимости. Можно делать например вот так:
public bool override Equals(object obj)
{
return Equals(obj as MyClass);
}
public bool Equals(MyClass other)
{
if (other== null)
return false;
// сравниваем объекты
}
Интерфейс IEquatable<>
был придуман для того чтобы избегать лишней упаковки значимых типов при сравнении.
До тех пор, пока все радовались объектам и использовали только их - у метода object.Equals(object)
особых недостатков не было (лишнее приведение типов особо страшным недостатком не является). Но когда в языке появились обобщенные типы - все стало куда хуже.
Попытка вызвать метод object.Equals(object other)
для значимого типа приводит к тому, что:
Вообще говоря, обобщенные типы придумали именно для того чтобы избегать лишних упаковок структур, поэтому необходимость упаковывать их только для того чтобы сравнить - катастрофа. Фактически, обобщенная коллекция System.Collections.Generics.Dictionary<,>
из-за постоянных упаковок может работать даже хуже чем более старый вариант System.Collections.Hashtable
.
Поэтому и был придуман новый интерфейс, который позволяет сравнить два значения без приведения типов (и, как следствие, без упаковки).
Теперь про ваш метод. Начну с того, что рекомендация из книги не совсем правильная: в стандартной библиотеке принято другое соглашение относительно подобных методов. Вместо того чтобы требовать реализации IEquatable<>
, можно воспользоваться классом EqualityComparer<>
, который внутри использует либо IEquatable<>
, либо старый метод object.Equals
если первый недоступен. Кроме того, желательно давать возможность передать в метод любую реализацию IEqualityComparer<>
(например, для сравнения строк без учета регистра).
Вот так будет правильнее:
public static bool IsIn<T>(T what, T[] obs, IEqualityComparer<T> comparer = null)
{
if (comparer == null) comparer = EqualityComparer<T>.Default;
foreach(T v in obs)
{
if (comparer.Equals(what, v))
{
return true;
}
}
return false;
}
(Ну и про вторую ошибку в методе я уже писал тут: В условном операторе выполняются обе ветки // поиск по массиву не работает)
С такой реализацией метода его можно использовать как с простыми классами (объекты таких классов сравниваются просто по ссылке):
class Student
{
public string Name;
public string Surname;
}
так и с более продвинутыми:
sealed class Student : IEquatable<Student>
{
public readonly string Name;
public readonly string Surname;
public Student(string name, string surname)
{
Name = name;
Surname = surname;
}
public override bool Equals(object other) => Equals(other as Student);
public bool Equals(Student other)
{
if (this == other) return true;
if (other == null) return false;
if (Name != other.Name) return false;
if (Surname!= other.Surname) return false;
return true;
}
public override int GetHashCode()
{
unchecked
{
// https://stackoverflow.com/a/263416/4340086
int hash = 2166136261;
hash = (16777619 * hash) ^ (Name?.GetHashCode() ?? 0);
hash = (16777619 * hash) ^ (Surname?.GetHashCode() ?? 0);
return hash;
}
}
}
Насчет "Не все ветви метода возвращают значение": если obs пустой, то код внутри foreach
никогда не выполнится, следовательно метод не встретит ни одного оператора return. поэтому ошибка.
Насколько я понимаю, код метода должен быть таким:
public static bool IsIn<T>(T what, T[] obs) where T : IEquatable<T>
{
foreach(T v in obs)
{
if (v.Equals(what))
{
return true;
}
}
return false;
}
Да, сигнатура данного метода подразумевает, что в данный метод могут быть переданы только объекты тех типов, которые реализовывают интерфейс IEquatable.
Виртуальный выделенный сервер (VDS) становится отличным выбором
Какая разница между этими двумя класами? Что в первом универсальный тип данных - что во второмБолее того, насколько я знаю, компилятор тип...