Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 118
Текст из файла (страница 118)
Версия ЕЧоа1з по умолчанию, предоставленная переопределением Ецоа1з в классе кка1оеТуре, от которого наследуются все типы значений, использует рефлексию для прохода по всем внутренним полям двух сравниваемых значений, проверяя их равенство. Наличие двух семантических значений Ечиа1з в СЬК может приводить к некоторой путанице, происходящей из того факта, что типы значений и ссылочные типы имеют разный семантический смысл для Ейоа1з. В этом разделе рассматривается реализация ОЬч еее.
Ецпа1з для ссылочных типов; то же самое для типов значений будет показано в следующем разделе. Ссылочные типы и эквивалентность идентичности Что подразумевается, когда тип относят к ссылочным типам? Это означает, что каждая переменная этого типа на самом деле является указателем на реальный объект в куче. В результате создания копии этой ссылки получается другая ссылка, которая указывает на тот же самый объект. 442 Глава ) 3 Рассмотрим следующий код: риьттс с1ввв Епигурогпе вивгтс ио16 Мати() ( оЬ)есе гегегепсел = пен Бувиеа.ОЬ)ест() оЬ)есе гегегепсев = гетегепсеаэ ) ) В методе мвап создается новый экземпляр Бувсем.
Оь)ест и сразу же делается копия этой ссылки. В результате получаются ссылочные переменные, как показано на рис. 13.1. Рис. 13.1. Ссылочные переменные В среде СЬК переменные, представляющие ссылки,в действительности являются типами значений, которые хранят местоположение в памяти (т.е. указатели на объекты, которые они представляют) вместе с ассоциированным типом.
Однако обратите внимание. что когда ссылка копируется, объект, на которую она указывает, не копируется. Вместо этого получаются две ссылки на один и тот же объект. Операции над объектом, выполненные через одну ссылку, будут видимы клиенту, использующему другую ссылку. Теперь посмотрим, что означает сравнение двух ссылок.
Что на самом деле означает эквивалентность двух ссылочных переменных? Это зависит от того, что нужно. и как определяется эквивалентность. По умолчанию эквивалентность ссылочных переменных означает их сравнение на идентичность. Две ссылочные переменные считаются эквивалентными, если они ссылаются на один и тот же объект, как показано на рис. 13.1. Эта ссылочная эквивалентность является поведением по умолчанию для определения эквивалентности двух ссылок на объекты, находящиеся в куче.
В клиентском коде необходимо проявлять осторожность при сравнении двух объектных ссылок на эквивалентность. Рассмотрим следующий код: риЬ11с с1ввв Епегуро1пи ( веве1с Ьоо1 ТевитогЕЧив11Су( оЬ)ест оЬ)1, оЬ)есе оЬ)2 ) ( гееигп оЬ)1.ЕЧив1в( оЬ)2 ); ) втвеьс иова Маза() ( оЬ)есс оЬ)1 = пен Бувсек.ОЬ)ест(); оЬ)ест оЪ12 = пи11) Бувсев.сопво1е.нгтсеьтпе( "оЬ)1 == оЬ)2 дает (0)", тевгрогЕЧив11гу(оЬ)1, оЬ)2) ) Е поисках канонических форм С№ 443 Здесь создается экземпляр Яуягев. ОЬЯесс и требуется узнать, эквивалентны ли переменные оЬ11 и оЬч 2.
Поскольку сравниваются ссылки, тест эквивалентности определяет, указывают ли они на один и тот же экземпляр объекта. Взглянув на этот код, легко заключить, что очевидным результатом будет оЬЯ 1 ! = оЬЯ 2, поскольку оЬч 2 равен пп11. Это то, что ожидается. Однако рассмотрим, что произойдет, если поменять порядок параметров в вызове ТеяГГогЕс)па11гу. Очень быстро обнаруживается, что код завершается крахом с выдачей необработанного исключения, где теясГогес)па11гу пытается вызвать Есруа1я на пи11-ссылке. Поэтому код потребуется модифицировать следующим образом; рпЬ1гс с1авв ЕпстуРо1пс ( ягас1с Ьоо1 Теятгогвс)па11ку( оЬЯесс оЬ11, оьбест оЬ22 ) ( гЕ( оЬ11 == пп11 аа оЬ22 == пп11 ) ( гесптп Ггпеу ) ЕЕ( оЬ21 == пп11 ) ( гегпгп Еа1яеу ) тегпгп оЬ21.ЕЧпа1в( оЬ22 ) всас1с чо1с) Иаьп() ( оЬ1есг оЬ21 = пеи Яуягеа.ОЬЯесг() у оЬ1есс оЬ22 = пп11у Яувтеп.Сопво1е.нггсеьгпе( "оЬ21 == оЬ22 дает (0)", тевтготечиа11гу(оь22, оь21) Яувтеп.Сопяо1е,ит1гещпе( "по11 == пп11 дает (О)", Теясгогкс)оа11ку(пп11, пп11) Теперь код может поменять порядок аргументов в вызове ТеягуогЕс)оа11гу, поэтому получается ожидаемый результат.
Обратите внимание, что для получения правильного результата также предусмотрена проверка случая, когда оба аргумента равны пп11. Теперь метод теяеГогес)па11су завершен. Может показаться, что для проверки двух ссылки на эквивалентность здесь предпринято слишком много работы. Зто действительно так, и проектировщики стандартной библиотеки .Р)ЕТ Ггэупекуог)с осознали эту проблему и предусмотрели статическую версию ОЬЯ еск. Ес)па1я, выполняющую подобное сравнение.
К счастью, благодаря существованию статической версии ОЬч еск. Ес)па1я, беспокоиться о создании кода ТеяСГотЕс)па11су в этом примере не нужно. Итак, проверка эквивалентности ссылок на объекты проверяет по умолчанию их идентичность. Однако могут быть случаи, когда такая проверка эквивалентности не имеет смысла. Рассмотрим неизменяемый объект, представляющий комплексное число; рпЬ1гс с1аяя Соар1ехиптЬег ( рпЬТгс Сояур1ехипзЬег( 1пк теа1, гпт гяаэгпагу ) ( ГЬЕв.теа1 = теа1у Гпгя.гааяьпагу = 1зая1пагуу ! Рг1часе Епг геа1у рггчасе гпс 1падгпагуу 444 глава (3 рпЬ11с с1азз ЕпсгуРогпс згаг1с тога маго() ( Соир1ехипаЪег гегегепсеА = пен Сотр1ехиптЬег( 1, 2 )г Соир1ехмпиЬег гегегепсеВ = пен Соир1ехипиЬег( 1, 2 ); Бузсеа.сопзо1е Нггсеьгпе( "Результат проверки на эквивалентность: (О)" гегегепсел == гегегепсев Вывод этого кода показан ниже: Результат проверки на эквивалентность: Ра1зе На рис.
13.2 показана диаграмма, представляющая компоновку памяти для этих двух ссылок. Рис. 13.2. Ссылки на Соглр1ехмяпЬег Такого результата можно было ожидать, если полагаться на обычный смысл эквивалентности двух ссылок, принятый по умолчанию. Однако это совсем не очевидно пользователю объектов Согпр1ехмоиЬег. Имело бы больше смысла, чтобы сравнение двух объектов, представленных на диаграмме, вернуло г где, поскольку значения двух объектов одинаковы. Для получения такого результата понадобится предоставить собственную реализацию эквивалентности таких объектов. Чуть ниже будет показано, как это делается, но сначала давайте кратко обсудим смысл понятия эквивалентности. Эквивалентность значений На основе материала, приведенного в предыгрщем разделе, смысл эквивалентности значений должен быть очеввс(ным.
Проверка эквивалентности двух значений возвращает ггпе, когда действительные значения полей, представляющих состояние объекта или его значение, эквивалентны. В примере Соир1ехмпглЬег из предыдущего раздела проверка эквивалентности значений даст Г гое, когда значения полей геа1 и 1глапа па ту эквивалентны в двух экземплярах этого класса. В среде СЕЯ, а, следовательно, и в С№, именно так и трактуется эквивалентность типов значений, определенных в виде структур.
Типы значений наследуются от Бузсея .Ра1пеТуре, и в Бузсею.уа1петуре переопределен метод ОЬтесГ.Ег(па1з. В уа1петуре. Ечпа1з иногда используется рефлексия для перебора полей типа значений при сравнении этих полей. Такая обобщенная реализация будет работать со всеми типами значений. Однако намного эффективнее переопределить метод Ечпз1з в типах структур, сравнивая поля напрямую. Хотя применение рефлексии для выполнения этой задачи — обычно подходящий способ, он чрезвычайно неэффективен. На заметку! Прежде чем реализация )га1петуре.
Ет(из1з обратится к услугам рефлексии, в ней выполняется пара быстрых проверок. Если два сравниваемых типа разные, эквивалентности нет. Если они одного типа, сначала проверяются типы содержащихся в них полей — относятся ли они к простым типам данных, которые можно сравнить побитно, Если так, то и весь тип можно сравнивать побитно. Невыполнение этих условий ведет к применению рефлексии. В поисках канонических форм С№ 445 Поскольку реализация уа1цетуре. ечца1з по умолчанию выполняет итерацию по значениям содержащихся полей, используя рефлексию, эквивалентность этих индивидуальных полей определяется на основе реализаций ОЬ№есс. ецца1з этих объектов. Таким образом, если тип значения содержит поле ссылочного типа, в зависимости от семантики метода ЕЧца1з, реализованного этим ссылочным типом, может быть получен неожиданный результат.
В общем случае включение ссылочных типов в тип значения не рекомендуется. Переопределение ОЬ1ес~. Ес~иа1в для ссылочных типов Часто возникает необходимость в переопределении семантического значения эквивалентности для объекта. Может потребоваться, чтобы эквивалентность для ссылочного типа трактовалась как эквивалентность значений в противоположность ссылочной эквивалентности, или идентичности. Или же, как будет показано в одном из следующих разделов, может быть специальный пользовательский тип, в котором нужно переопределить метод Ецца1з по умолчанию, предоставленный Зузгев. уэ1цетуре, чтобы повысить эффективность операции сравнения. Независимо от причин переопределения ЕЧца1з, необходимо следовать перечисленным ниже правилам.










