#c_sharp #net
Разбирая тему обобщений (по Шиелду 4.0), потребовалось написать обобщённый метод, который вернет логическое значение true, если в массиве содержится некоторое значение. Далее в книге поясняется, что так как T - обобщённый тип, то для сравнения объектов обобщённого типа необходимо что бы класс этих объектов реализовывал интерфейс IEquatable с параметром T или IEquatable. Написано так же, что в данных интерфейсах определен метод для сравнения Equals(). Отсюда сразу же несколько вопросов: Во-первых, почему для сравнения объектов обобщённого типа нам вообще необходимо реализовывать какие-либо интерфейсы? Метод Equals() относится к методам определённым в Object => есть в каждом объекте. Я понял что для сравнения объектов в данном примере нужно реализовать описанные выше интерфейсы, но я не понял, ПОЧЕМУ это нужно сделать. Во-вторых, по Шиелду, как я написал выше, для сравнения объектов в обобщённом методе предлагается реализовать Equtable с параметром T или IEqutable. Один параметризирован, другой нет. Вопрос - какой в каком случае необходимо использовать? Далее Шиелд в своей книге демонстрирует пример метода, который проверяет, находится ли в массиве некоторое значение. Вот этот метод: public static bool IsIn(T what, T[] obs) where T : IEquatable { foreach(T v in obs) { if (v.Equals(what)) { return true; } else { return false; } } } Тут так же есть два вопроса. Во-первых, начну с того, что данный метод не работает. Студия выдает ошибку компиляции "Не все ветви метода возвращают значение". Я несколько раз перепроверял написанный код, но ошибка остается. Как это исправить? Во-вторых, я не уверен, правильно ли я понял, что сигнатура данного метода подразумевает, что в данный метод могут быть переданы только объекты тех типов, которые реализовывают интерфейс IEquatable? Другими словами, если я захочу передать в метод массив каких нибудь студентов, и найти в нём Васю Пупкина, то мой класс студента должен выглядить следующим образом? class Student : IEquatable { public string Name; public string Surname; public bool Equals(T other) { //some code for compare return false; } } Прошу последовательно ответить на все вопросы. Спасибо
Ответы
Ответ 1
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, в котором можно сразу сравнивать 2 объекта, без проверок их типов (что тоже занимает некоторое время). По-хорошему теперь вы должны реализовать IEquatable для всех классов, которые можно сравнивать. При этом вы должны не забывать про переопределение старого Equals, для совместимости. Можно делать например вот так: public bool override Equals(object obj) { return Equals(obj as MyClass); } public bool Equals(MyClass other) { if (other== null) return false; // сравниваем объекты } Ответ 2
Интерфейс IEquatable<> был придуман для того чтобы избегать лишней упаковки значимых типов при сравнении. До тех пор, пока все радовались объектам и использовали только их - у метода object.Equals(object) особых недостатков не было (лишнее приведение типов особо страшным недостатком не является). Но когда в языке появились обобщенные типы - все стало куда хуже. Попытка вызвать метод object.Equals(object other) для значимого типа приводит к тому, что: нулевой параметр (this) упаковывается и размещается в куче, первый параметр (other) упаковывается и размещается в куче, оба параметра распаковываются обратно и сравниваются, упакованные копии параметров забываются (и в следующий раз упаковка будет происходить заново). Вообще говоря, обобщенные типы придумали именно для того чтобы избегать лишних упаковок структур, поэтому необходимость упаковывать их только для того чтобы сравнить - катастрофа. Фактически, обобщенная коллекция System.Collections.Generics.Dictionary<,> из-за постоянных упаковок может работать даже хуже чем более старый вариант System.Collections.Hashtable. Поэтому и был придуман новый интерфейс, который позволяет сравнить два значения без приведения типов (и, как следствие, без упаковки). Теперь про ваш метод. Начну с того, что рекомендация из книги не совсем правильная: в стандартной библиотеке принято другое соглашение относительно подобных методов. Вместо того чтобы требовать реализации IEquatable<>, можно воспользоваться классом EqualityComparer<>, который внутри использует либо IEquatable<>, либо старый метод object.Equals если первый недоступен. Кроме того, желательно давать возможность передать в метод любую реализацию IEqualityComparer<> (например, для сравнения строк без учета регистра). Вот так будет правильнее: public static bool IsIn(T what, T[] obs, IEqualityComparer comparer = null) { if (comparer == null) comparer = EqualityComparer .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 { 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; } } } Ответ 3
Насчет "Не все ветви метода возвращают значение": если obs пустой, то код внутри foreach никогда не выполнится, следовательно метод не встретит ни одного оператора return. поэтому ошибка. Насколько я понимаю, код метода должен быть таким: public static bool IsIn(T what, T[] obs) where T : IEquatable { foreach(T v in obs) { if (v.Equals(what)) { return true; } } return false; } Да, сигнатура данного метода подразумевает, что в данный метод могут быть переданы только объекты тех типов, которые реализовывают интерфейс IEquatable.
Комментариев нет:
Отправить комментарий