Модераторы: diadiavova, gambit
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Реализация IEqualityComparer(Of T) 
V
    Опции темы
diadiavova
Дата 24.10.2009, 23:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Доктор Зло(диагност, настоящий, с лицензией и полномочиями)
****


Профиль
Группа: Модератор
Сообщений: 5821
Регистрация: 14.8.2008
Где: В Коньфпольте

Репутация: 8
Всего: 142



В ряде случаев возникает необходимость реализации интерфейса IEqualityComparer(Of T). Это может понадобиться для работы инструкции Distinct в LINQ-запросах или соответствующей функции, для удаления из коллекции повторяющихся элементов, в ситуации, когда равенство объектов надо определить по-своему; и для коллекции 
System.Collections.Generic.HashSet(Of T), которая не принимает двух одинаковых объектов, однако их "одинаковость" может определяться при помощи объекта реализующего данный интерфейс.

В целом реализация этого интерфейча не вызывала бы сложностей, если бы не одно "но", а именно - функция GetHashCode.

Начнём по порядку. 
System.Collections.Generic.IEqualityComparer(Of T)
Имеет два метода:
Equality - принимает два аргумента сравниваемого типа и возвращает булево значение(True - если объекты следует считать равными, и False - в ином случае). Именно в этом методе реализуется вся логика сравнения.

GetHashCode - принимает объект сравниваемого типа и возвращает соответствующее ему число типа Integer.

Со вторым методом как раз и порой и возникают проблемы. В общем-то ограничений на то, каким должно быть это число нет, но есть одно правило: если метод Equals для двух объектов возвращает True, то метод GetHashCode должен для них обоих возвращать одно и то же число, в противном случае числа должны быть разными.
О том, как реализовать этот метод здесь и пойдёт речь.

На самом деле идея достаточно проста:
Создаём параметризированный абстрактный(MustInherits) класс, в котором метод GetHashCode уже будет реализован и привязан к методу Equals(не реализованному). Для учёта сравнивавшихся ранее объектов создадим в классе поле типа Dictionary, а для того, чтобы иметь неповторяющиеся хэш-коды счётчик(поле типа Integer). Оба поля должны быть статическими, поскольку должны учитывать все объекты сравниваемого типа, независимо от того, какой экземпляр нашего компоратора использовался ранее для сравнения объектов.
Сразу хочу оговориться, что то, что мы сейчас создадим не будет как следует работать во всех случаях. Это всего лишь направление, в котором можно "копать", и готовый код для использования в самых простейших случаях.

Вот собственно код компаратора.
Код

Public MustInherit Class MyEqComparer(Of T)
    Implements IEqualityComparer(Of T)
    ''' <summary>
    ''' Этот метод надо реализовать в производном классе
    ''' </summary>
    ''' <param name="x"></param>
    ''' <param name="y"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public MustOverride Function Equals1(ByVal x As T, ByVal y As T) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of T).Equals
    ''' <summary>
    ''' Здесь содержится вся логика нахождения хэш-кодов
    ''' </summary>
    ''' <param name="obj"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetHashCode1(ByVal obj As T) As Integer Implements System.Collections.Generic.IEqualityComparer(Of T).GetHashCode
        'перебираем ключи в словаре
        For Each k In codeDictionary.Keys
            If Equals1(k, obj) Then
                'если среди ключей найден такой, который при сравнении методом Equals1 возвратит True
                'возвращаем соответствующий ему код из словаря
                Return codeDictionary(k)
            Else
                'Если в словаре такой объект не нашёлся
                'Увеличиваем счётчик на единицу
                CodeCounter += 1
                'добавляем в словарь новую пару 
                codeDictionary.Add(obj, CodeCounter)
                'возвращаем новое значение счётчика
                Return CodeCounter
            End If
        Next
        Return -1
    End Function
    ''' <summary>
    ''' Словарик для хранения ранее проверявшихся объектов
    ''' и соответствующие им коды
    ''' </summary>
    ''' <remarks></remarks>
    Shared codeDictionary As New Dictionary(Of T, Integer)
    ''' <summary>
    ''' Счётчик кодов. Всякий раз при назначении нового кода он будет увеличиваться и 
    ''' это и будет новый код
    ''' </summary>
    ''' <remarks></remarks>
    Shared CodeCounter As Integer = 0
End Class

Как этим пользоваться?
Для тестирования создадим простой класс с двумя полями и конструктором, инициирующим эти поля.
Код

Public Class MyItemType
    Public Sub New(ByVal int As Integer, ByVal str As String)
        IntField = int
        StrField = str
    End Sub
    Public IntField As Integer
    Public StrField As String
End Class


Далее наследуем созданный нами компоратор, так, чтобы он сравнивал экземпляры нашего класса MyItemType
Код

Public Class MyItemComparer
    Inherits MyEqComparer(Of MyItemType)

    Public Overrides Function Equals1(ByVal x As MyItemType, ByVal y As MyItemType) As Boolean
        Return x.IntField = y.IntField AndAlso x.StrField = y.StrField
    End Function
End Class

На что здесь следует обратить внимание, так это на то, что мы реализуем в нашем классе-наследнике только метод Equals, независимо от того, какую логику мы в него заложим.
Далее для проверки функциональности создадим такую функцию
Код

    Public Function TestComparer() As Integer
        Dim mc As New MyItemComparer
        Dim l As New List(Of MyItemType) 
        l.Add( New MyItemType(1, "qqq"))
        l.Add( New MyItemType(3, "rrr"))
        l.Add( New MyItemType(2, "ooo"))
        l.Add( New MyItemType(1, "qqq"))
        l.Add( New MyItemType(3, "rrr"))
        l.Add( New MyItemType(2, "ooo"))
        Return l.Distinct(mc).Count
    End Function

Вызвав эту функцию мы получим результат 3. Потому, что метод Distinct(кстати, для того, чтобы он был доступен в код надо импортировать пространство имён System.Linq)с нашим компоратором посчитает последние три объекта уже присутствующими в коллекции и не возвратит их. Если в последних трёх элементах поменять значение строк
Код

    Public Function TestComparer() As Integer
        Dim mc As New MyItemComparer
        Dim l As New List(Of MyItemType)
        l.Add( New MyItemType(1, "qqq"))
        l.Add( New MyItemType(3, "rrr"))
        l.Add( New MyItemType(2, "ooo"))
        l.Add( New MyItemType(1, "ghj"))
        l.Add( New MyItemType(3, "5;lr"))
        l.Add( New MyItemType(2, "juy"))
        Return l.Distinct(mc).Count
    End Function

то все шесть элементов останутся в коллекции, что в общем-то нормально(ведь объекты не равны). Но если признаком равенства мы будем считать равенство только по полю IntField, а значение поля StrField в расчёт принимать не будем.  То есть наш компоратор будет выглядеть так
Код

Public Class MyItemComparer
    Inherits MyEqComparer(Of MyItemType)
    Public Overrides Function Equals1(ByVal x As MyItemType, ByVal y As MyItemType) As Boolean
        Return x.IntField = y.IntField
    End Function
End Class

То наша функция снова будет возвращать 3. Не смотря на то, что строковые поля не равны, при сравнении они учитываться не будут. То есть всё работает.


Есть правда у этого приёма и недостатки. 
Например, если объект, для которого уже ранее вызывался метод GetHashCode изменится и станет "равным" другому объекту, который уже тоже содержится в коллекции, то коды они выдадут разные, и тут возможно множество вариантов, ну да я с самого начала указал, что всех проблем этот пример не решит.

Это сообщение отредактировал(а) diadiavova - 25.10.2009, 00:30


--------------------
Хочешь получить мудрый совет - читай подписи участников форумов.
Злой доктор Щасзаболит smile
PM   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума VB .NET
diadiavova
  • Прежде чем задать вопрос, воспользуйтесь поиском: возможно Ваш вопрос уже обсуждался и на него был получен ответ.
  • Если такой же вопрос не найден, не стоит задавать свой вопрос в любую тему, создайте новую.
  • Заголовок темы должен отображать ее суть.
  • Содержание поста должно описывать проблему понятно, но в то же время, по возможности, лаконично. Сначала следует описать суть вопроса, потом можно привести пример кода, не вынуждайте других участников угадывать в чем Ваша проблема - телепатов здесь нет.
  • Будьте взаимно вежливы и дружелюбны.
  • При оформлении сообщений используйте форматирование, примеры кода заключайте в теги [CODE=vbnet][/CODE].
  • Также ознакомьтесь с общими правилами, действующими на всем форуме.
  • Если вопрос решен, не забывайте помечать тему решенной(вверху темы есть ссылка). Кроме того, если Вы хотите отблагодарить участников, оказавших помощь в решении, можно повысить им репутацию, в случае, если у Вас менее 100 сообщений в форуме и функция изменения репутации Вам недоступна, можете написать сюда.
  • Общие вопросы по программированию на платформе .NET обсуждаются здесь.
  • Литература по VB .NET обсуждается здесь.

Если Вам помогли и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, diadiavova.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | VB .NET | Следующая тема »


 




[ Время генерации скрипта: 0.0459 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.