Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 120
Текст из файла (страница 120)
Если зто состояние меняется, то результат бесназпсос)е, скорее всего, изменится вместе с ним. Реализации 6етназЬСог)е должны подчиняться перечисленным ниже правилам. ° Если для двух экземпляров х.ег)ца1з )у) равно сгце, то х. 6есназпсог)е ) ) у.6етназЬСог)е )). ° Хеш-коды, сгенерированные бегназпсос)е, не обязательно должны быть уникальными. ° В 6еСНазЬСог)е не разрешено генерировать исключения. Если два экземпляра возвращают одинаковые значения хеш-кода, они должны далее сравниваться методом Ес)са1з, чтобы определить, эквивалентны ли они. Кстати, если метод 6еСНазЬСог)е очень эффективен, на его основе можно построить реализации операций орегасог! = и орегасог==, поскольку разные хеш-коды объектов одного типа предполагают отсутствие эквивалентности.
Такая реализация операций в некоторых случаях может оказаться более эффективной, но все зависит от эффективности самой реализации 6егназЬСог)е и сложности метода Ес)оа1з. В некоторых случаях при Е поисках канонических форм С№ 449 использовании этого приема вызовы операций могут оказаться менее эффективными, чем простой вызов Ес[иа1в, а в других случаях они могут быть заметно более эффективными. Например, рассмотрим объект, который моделирует многомерную точку в пространстве.
Предположим, что количество измерений (ранг) этой точки может приближаться к сотням. Внутренне измерения точки можно представить с помощью массива целых чисел. Предположим, что необходимо реализовать метод бегНавЬСобе, вычисляя код СКС32 для точек измерений в массиве. Это также подразумевает, что тип Ро1пт является неизменяемым. Вызов 6еСНавЬСос1е потенциально может оказаться дорогостоящим, если код СКС32 будет вычисляться при каждом вызове. Поэтому, возможно, имеет смысл заранее вычислить хеш-код и сохранить его в объекте. В таком случае операция эквивалентности может выглядеть, как показано ниже.
веа1еб риЫтс с1ввв Рокпт // Прочие методы опущены для ясности. риЫ1с очеггкбе Ьоо1 ЕЧив1в( оЬ]ест отЬег ) ( Ьоо1 теви1г = Еа1век Ротпт ГЬвт = отпег ав Ротпт; 11( ГЬвт 1= пи11 ) ( 11( тЫв.соогбьпвтез.ЬепдтЬ 1= кпвт.соогбьпатев.ЬепеСЬ ) ( геви1г = Рв1век ) е1ве ( теви1Г = Гтие; Еог( 1опс 1 = О) 1 < гыв.соотб1пвтев.ьепдтьк ) ( 11( ГЫв.соотб№пвтев[1] 1= тьвт.соогбхпвтев[1] ) ( геви1г = йв1ве; Ьгеакк ) ) ) тетитп теви1Г; ) риЫ1с очегтьбе 1пт ОеГНввЬСобе() гегигп ргесотритебНввЬ; ) риЫтс втвтьс Ьоо1 орегвтог ==( Рохас рт1, Рогат рт2 ) ( 11( рт1.6етНавЬСобе() 1= рт2.6еГНввЬСобе() ) ( гетигп йа1век ] е1ве 1 гетигп ОЬ№ест.ЕЧив1в( рт1, рт2 )' ) риЬ11с втвтто Ьоо1 оретвтог 1=( Ротпт рт1, Ро1пс рс2 ) ( 11( рт1.6еСНввЬСобе() 1= рт2.6етНавЬСобе() ) ( гегигп стоек е1ве ( гетигп !ОЬ]ест.Ес[иа1в( рт1, рт2 ); ) ) рг1чвте 11овт[] соотбтпвтев; ргьчаге тпт ргесощритебНввЬ! 450 Глава (3 пя1пд Яуятев( рпЬ1тс яеа1ес( с1аяя Совр1ехкпвЬет ( РпЬ11с Совр1ехкпвьет( с(опЬ1е теа1, СопЬ1е 1ваятпату ) ( СЬтя.теа1 = теа1; Скья.твадтпату = 1вад1пату( ) рпЬ1тс оссетт1с(е Ьоо1 Ес(па1я( ЬЬ)ест сСЬет ) ( Ьос1 теяп1С = са1яес Совр1ехкпвЬет ГЬат = оСЬет ая Совр1ехкпвЬет; 11( СЬаг != пп11 ) ( теяп1С = (СЬтя.теа1 == сЬаг.теа1) ЯЯ (Сйья,твачтпату == СЬаг.1ваЯ1пату); ) тегитп теяп1С; ) рпЬ11с охетттяе 1пг ЯеткаяЬСобе() тегптп (тпг) МаСЬ.Яс(тс( МаСЬ.Рок(СЬ1я.тея1, 2) * мать.Рсэ(сп1я.1вад1пату, 2) )( рпЬ11с ягагтс Ьсс1 оретагот ==( Совр1ехкпвЬет ппв1, Совр1ехксвЬет ппв2 ) ( тетптп ОЬ)ест.ЕЧпя1я(ппв1, ппв2)с рпьутс ягагтс Ьоо1 сретагот !=( тетпгп !ОЬ)есС.ЕЧпа1я(ппв1, ) Совр1ехкпвЬет ппв1, Совр1ехкпвьет ппв2 ) ( ппв2); В этом примере, пока предварительно вычисленное хеш-значение достаточно уникально, перегруженные операции в некоторых случаях будут выполняться быстро.
В худших случаях вместе с вызовами функции для их получения потребуется одно дополнительное сравнение двух целых чисел — хеш-значений. Если вызов ес(иа1я дорог, то такая оптимизация даст определенный выигрыш для большого количества сравнений. Если один вызов Ес(па1з не является дорогим, то такая техника увеличивает накладные расходы и снижает эффективность кода. Лучше вспомнить старую истину, что преждевременная оптимизация — это плохая оптимизация. Такую оптимиаацию следует применать только после того, как профилировщик направит в этом направлении, и есть уверенность, что оптимизация должна помочь.
Метод ОЬ)есс.ОеснаяЬсос(е существует потому, что разработчики стандартной библиотеки решили, что будет удобно иметь возможность использовать любой объект в качестве ключа хеш-таблицы. На самом деле не все объекты могут рассматриваться как хорошие кандидаты в ключи хеша. Обычно в качестве таких ключей лучше использовать неизменяемые типы. Неплохим примером неизменяемого типа из стандартной библиотеки может служить я уз сев.
я г т1по. Однажды созданный объект этого типа изменить невозмоксно. Поэтому вызов ЯегкаяЬСос(е на экземпляре строки гарантированно всегда возвращает о)П(о и то же значение для одного и того же экземпляра строки. Намного сложнее генерировать хеш-коды для изменяемых объектов. В таких случаях лучше основывать реализацию ЯегкаяЬСос(е на вычислениях, выполняемых по неизменяемым полям внутри изменяемого объекта.
Детальное описание алгоритмов генерации хеш-кодов выходит за рамки этой книги. За исчерпывающей информацией рекомендуется обратиться к фундаментальному труду Дональда Кнута Искусство программирования. слом 3. Сорптироэка и поиск (ИД "Вильямс", 2007 г). Для примера предположим, что нужно реализовать НегнаяЬСос(е для типа Совр1ехкаве. Одно из возможных решений предусматривает вычисление хеша на основе модуля комплексного числа, как показано в следующем примере; В поисках канонических форм СЗ 461 т'т' Прочие методы опущены лля ясности.
рт|чвте теадоп1у доиЬ1е теа1~ рт|уате теадоп1у доцп|е тиас|латук ) Предложенный алгоритм ПетнавЬСоде не является образцом высокой эффективности. На самом деле он вообще не вффективен, поскольку основан на нетривиальных математических процедурах с плавающей точкой. К тому же округление потенциально может привести к тому, что множество комплексных чисел попадут в один сегмент хеша. В этом случае эффективность хеш-таблицы снижается.
В качестве самостоятельного упражнения попробуйте реализовать более аффективный алгоритм. Обратите внимание. что из соображений эффективности для реализации оретатот! = метод СетнавЬСоде не используется. Но что более важно. сравнение комплексных чисел на предмет эквивалентности основано на статическом методе Оь1ест. ет)ца1в. Этот удобный метод выполняет проверку ссылки на равенство пц11 перед вызовом метода Ес(ца1в экземпляра, избавляя от необходимости кодировать такую проверку вручную.
Если бы для реализации операции оретатот (= применялся метод СетнавЬСоде, пришлось бы проверять ссылки на по11 перед вызовом ПетнавЬСоде на них. Также заметьте, что оба поля. используемые для вычисления хеш-кода. являются неизменяемыми. Поэтому акземпляр этого объекта всегда возвращает одно и то же значение хеш-кода на протяжении всего его существования.
Кроме того, однажды вычислив хеш-код, можно его квшировать. чтобы добиться повышенной эффективности. Поддерживает ли обьект упорядочивание? Иногда приходится проектировать класс объектов, предназначенных для хранения в коллекции. Если объекты в коллекции должны иметь возможность сортироваться, например, вызовом Яотт на Аттау11вт, потребуется четко определенный механизм для сравнения двух объектов. Шаблон, предложенный разработчиками базовой библиотеки классов, предусматривает реализацию такими объектами следующего интерфейса 1СовратаЫе: рпЬ1тс 1птетгасе 1СоиратаЬ|е ( 1пт СотратеТо( оЬ|ест оЬ1 ) Таблица 13.1.
Смысл возвращаемых значений метода 1С<ж(рака)>1е. СкяэракеТо Возвращаемое значение СовратвТо Положительное Нулевое Отрицательное Смысл тЬ|в > оЬ1 ел|в == оьб тЬ|5 < ОЬ1 Для большей безопасности в отношении типов следует рассмотреть воэможность использо- вания обобщенного интерфейса 1СовратаЬ1е<Т>, как показано з главе 11. Это снова интерфейс с единственным методом. К счастью, 1сотратаЫе не таит в себе такой глубины и ловушек, как интерфейсы 1С1опеаЬ1е и 10твроваЫе.Метод Сотрэтето достаточно прямолинеен.
Он может возвратить положительное, отрицательное или нулевое значение. Смысл возвращаемых значений описан в табл. 13.1. 452 Глава 13 При реализации 1СоспратаЫ е. СоспрагеТо необходимо помнить о нескольких моментах. Во-первых, обратите внимание, что спецификация возвращаемого значения ничего не сообщает о действительном значении возвращенного целого числа. Определен только знак возвращаемых значений. Поэтому для того, чтобы обозначить ситуацию, когда гЬтв меньше оЬО, можно просто вернуть -1. Когда объект представляет значение. которое имеет целочисленный смысл, то эффективный способ вычисления возвращаемого значения состоит в вычитании одного из другого. Может возникнуть соблазн трактовать возвращаемое значение как степень неравенства.
Хотя это возможно, но не рекомендуется, поскольку это выходит за рамки спецификации 1СопсрагаЬ1е. и не от всех объектов можно такого ожидать. Имейте в виду, что операция вычитания над целыми числами может привести к переполнению. Если вы хотите избежать этой ситуации, для большей безопасности обратитесь просто к методу 1соспрэгаЬ1е. Сопсрагето, реализованному целочисленным типом. Во-вторых, имейте в виду, что в Соспрагето не предусмотрено никакого определенного воавращаемого значения, когда два объекта не могут быть сравнены.
Поскольку типом параметра Соспрзтето является яувгепс. ОЬО ест, несложно попытаться сравнить экземпляр Арр1е с Отапое. В таких случаях сравнение невозможно. и это должно быть указано генерацией исключения АтдовепгЕхсергтоп, И, наконец, семантически интерфейс 1сопсра гаЬ1е является надмножеством ОЬ О ест . Ес(па1в. Если осуществляется наследование от класса, который переопределяет Ес(оа1в и реализует 1СожратаЬ1е, то разумно будет и переопределить Ес(иа1в, и заново реализовать 1СоспрзтзЬ1е в производном классе, либо же не делать ни того, ни другого. Следует удостовериться в согласованности реализаций Ес(оз1в и Соспрагето друг с другом.









