Страницы

Поиск по вопросам

суббота, 30 ноября 2019 г.

Зачем мы реализовываем IEquatable, если Equals() есть в Object?

#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.

Комментариев нет:

Отправить комментарий