Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 125
Текст из файла (страница 125)
Канонические формы типов значений Прн исследовании канонических форм типов значений обнаруживается, что некоторые концепции, применимые к ссылочным типам, могут использоваться н здесь. Однако существует немало заметных отличий. Например, реализация интерфейса 1С1опезЬ1е для типа значений не имеет смысла. Формально это можно сделать, но поскольку 1С1опеаЬ1е возвращает экземпляр ОЬ) есг, реализация 1С1опеаЬ1е. С1опе для типа значений вернет просто упакованную копию самого себя. В точности то же поведение можно получить.
выполнив простое приведение экземпляра типа значения к ссылке на я уз сев. Оь) ес г, при условии, что тип значений не содержит в себе никаких ссылочных типов. В принципе, можно было бы возразить, что типы значений, содержащие в себе изменяемые ссылочные типы, свидетельствуют о плохом дизайне. Для тех. кому интересно, почему так названо поле: ознакомнгесь с идиомой Рвпр! (ро)п(ег (о рниаге )шр!егпеп(анап — указатель на приватную реалнэацшо) в книге )Ьрба Саттера (НегЬ Бинег) Новые сложные задачи на С++. Серия "С++!и-Юер(К' (ИД "Вильямс", 2008 г). в В действительности многие типы значений, определенные в .)ЧЕТ г гашевог)к, являются неизменяемыми. 466 Глава ) 3 Типы значений больше всего подходят для представления неиаменяемых легковесных порций данных.
Поэтому до тех пор, пока ссылочные типы. содержащиеся в типах значений, являются неизменяемыми — например, подобно типу Бузсев. Ясг1пд — беспокоиться о реализации 1С1опеаЬ1е для типа значений не нужно. Если все-таки возникает потребность в реализации 1С1опеаЬ1е в типе значений, следует присмотреться к его дизайну. Возможно, тип значения должен быль заменен ссылочным типом. Типы значений не нуждаются в финализаторе, и фактически Се не позволит создать финалиэатор для структуры в синтаксисе деструктора.
Аналогично, типы значений не нуждаются в реализации интерфейса 1р1врозаЬ1е, если только не содержат объектов по ссылке, реализующих 1разроз аЬ1е, или же не потребляют значительных системных ресурсов. В таких случают типы значений должны реализовывать 1 па зрозаь1е. С типами значений, реализующими 101зрозаЬ1е, можно применять оператор иаьпд. Совет. Поскольку типы значений не могут реализовывать финализаторы, они не гарантируют выполнение кода очистки в р1зрозе, если пользователь забудет вызвать его явно.
Таким образом, объявлять поля ссылочного типа внутри типа значений не рекомендуется. При наличии поля типа значений, которое требует вызова р1зрозе, ничто не гарантирует, что этот вызов будет выполнен. Типы значений и ссылочные типы разделяют между собой множество идиом реализации. Например, для обоих имеет смысл рассмотреть реализацию интерфейсов 1соврагаь1е, 1еогпаггаь1е и. возмовсно, 1сопчегг1ь1е. В оставшейся части раздела рассматриваются различные канонические концепции, которые должны применяться при проектировании типов значений. В частности, при переопределении еопа1з для повышения эффективности во время выполнения необходимо знать, что означает реализация того или иного интерфейса для типа значений.
Итак, приступим. Переопределение жс)иа1в для повышения производительности Основные отличия между двумя видами эквнвалентнсюти в СЬН и СВ уже были показаны ранее. Так, например. известно, что ссылочные типы (экземпляры классов) по умолчанию определяют эквивалентность как проверку на идентичность, а типы значений (экземпляры структур) — как проверку равенства значений. Ссылочные типы получают свою реализацию по умолчанию от ОЬ)есГ.Еопа1з, в то время как типы значений — от переопределения ес)па1з из Яузгеп.ча1иетуре. Все типы структур (и перечислений) неявно унаследованы от Б узг ев.
ча1 иет уре. для каждой определяемой структуры должно быть реализовано собственное переопределение еооа1з. Сравнивать поля объекта можно более эффективно, поскольку известны их типы и что они представляют собой во время компиляции. Давайте обновим пример Совр1ехнипЬег из предыдущих разделов, преобразовав его в структуру и реалиаовав собственное переопределение Ес)па1з: пзапд Яузсепк роЪ11с вггзеа Совр1еяввввег: ТСотрагвЪ1е рив11с Сотр1ехкппЬег( Ооипае геа1, ОоиЬ1е авад1пагу ) ( Гбаз.геа1 = геа1) ГЬ1з.1вая1пагу = 1взо1пагу; ) риЫ1с отегг1п)е Ьоо1 Едиа1в( оЬ»ест оСЬег ) ( Ьоо1 гези1Г = Та1век г»( оснег тв Соар1ехниаиег ) ( Совр1ехниаЬег Гнат = (Совр1ехнивЬег) оСЬег гези1Г = Хптегпа1Едиа1з ( СЬат ); риЬ11с всат1с Ьоо1 орегатог ==( Совр1ехниаЬег Совр1ехмивЬег гетитп пив1.Едиа1в(пиа2)> пшп1, тппп2 ) ( ) риЫтс всат»с Ьоо1 орегатог (=( Совр1ехнивЬег Совр1ехмиаЬег гетигп !пив1.Едиа1в(пив2); пиа1, пшп2 ) ( ) риЬ1тс тпг СоврагеТо( оЬ)ест огнег ) ( и»( !(отиег тв Совр1ехншпЬег) ) ( гитон пен АгдивепГЕхсерт1оп( "Неверное сравнение!" ) ! Совр1ехншпЬет ГЬат = (Совр1ехнивЬег) оГЬег) тпс геви1т) 11( 1птегпа1Едиа1з(ГЬат) ) ( геви1т = О) ] е1ве 1й( ГЫв.мадо»тобе > ГЬаГ.Мадп1сип(е ) [ геви1Г = 1; ) е1ве ( гези1Г = -1; гетигп геви1Г; ) рг1иаге ьоо1 1пгегпа1едиа1в( совр1ехмивьег гьаг ) ( гегитп (гыв.геа1 пьат.геа1) за (ГЫз.твад1пагу == ГЬат.1вад1пагу)к ) риЬ1тс ооиЬ1е Мадп1тип(е ( дег ( тесигп МаГЬ.Ядтт( Матн.рон(тнтв.геа1, 2) МаГЬ.Рон(ГЬтв.'тад1пагу, 2) ); ) ! // Проыие методы опущены для ясности.
рг»тате геабоп1у п(оиЫе геа1; рг1уате геап(оп1у п(оиЫе Евадтпагу; ) риЫтс зеа1ег( с1авв ЕптгуРо1пт ( вгат1с иотп( Мати () ( Совр1ехншпЬег пив1 пен Совр1ехншпЬет( 1, 2 ) ) Совр1ехнивЬег пив2 пен Совр1ехнивЬег( 1, 2 ) ) Ьоо1 гези1Г пив1. Едиа1в ( пив2 ) ) гетигп геви1Г; ! риЫТс отеггкбе 1пт ЯетнавЬСоп(е() ( гегигп (Рпг) сыз.мадп»гиде) В поисках канонических форм С» 467 468 Глава 13 Как показывает код, версия со ссылочным типом потребовала лишь минимальных изменений. Теперь тип объявлен как структура, а не класс, и он также поддерживает интерфейс 1СоврагаЬ1е.
О реализации интерфейсов структурами речь еще пойдет в разделе. озаглавленном "Поддерживают лн значения этого типа какие-то интерфейсы7". Дотошный читатель может заметить, что эффективность этого варианта далека от совершенства. Дело, конечно же, в упаковке и распаковке.
Вспомните, что каждый раз, когда экземпляр типа значения передается в виде объекта в списке параметров метода, он должен быть неявно упакован, если не был упакован ранее. Это значит, что когда метод Ма1п вызывает метод ес(оа1я, он должен сначала упаковать значение пцв2. Что еще хуже — методу затем приходится распаковывать значение, чтобы использовать его. Таким образом, в процессе сравнения двух значений на эквивалентность приходится дважды копировать одно иа них. Чтобы решить эту проблему, можно определить две перегрузки Едиа1я. Необходимо построить безопасную в отношении типов версию, принимающую Совр1ехнивЬег в качестве типа параметра, и также переопределить метод ОЬ) есг. ес(иа1я, как и раньше. На заметку! В .МЕТ ргагпевогк эта концепция формализована с помощью обобщенного интерфейса 1едсягаые<т>, в котором объявлен один метод, являющийся безопасной к типам версией едоа1я.
Посмотрим, как изменился код: иязпд Яуягего риЬ11с ясгосг Совр1ехпивЬег: 1СоврэгаЬ1е, 1СшпрагаЬ1ессовр1ехноввег>, 1есузаеаь1е<совр1ехмиввег> ( риЬ11с Совр1ехновсег( босЬ1е геа1, бооЬ1е твад1иагу ) ( ГЫя.геа1 = гея1) гшя.звадгпагу = 1вад1пагу) риЬ11с Ъоо1 Есуьа1е( Совр1ех)влпЬег оеЬег ) ( гейши (ЕЫ|.геа1 = огпег.геа1) аа (ЕЫз.звадгпагу =- оЕЬег.зяидзпагу)г ) рсЫзс стеггсбе Ьоо1 Едса1я( оЬ)есс оспег ) ( Ьоо1 геяс1Г = га1яе) 11( огпег 1я Совр1ехишпЬег ) ( Совр1ехкшпЬег спас = (Совр1ехковЬег) оспег геэи1Е Есута1в( СЬае ); ) гегогп геяи1гг ) раас отегггбе 1иг СеснаяЬСобе гессгп (зпг) Гпзя.надпзсобе) рсЫзс ясак(с Ьоо1 орегасог ==( Ссвр1ехновЬэг исв1, Совр1ехншпЬег пив2 ) ( гегсгп пив1.Едса1я(псв2)г ) рсЫзс ясагзс Ьоо1 орегагсг !=( Совр1ехисвЬег пив1, Совр1ехисвсег пив2 ) ( гегсгп !пив1.ечса1я(пив2)) ) Е поисках канонических форм С№ 469 риЬ11с кпс Сощрвгето( оЬ)есТ осиек ) ( 11( !(огьег кв Сощр1ехишпЬег) ) ( титом пеи пгдищептехсерткоп( "неверное сравнение!" ); ) гееигп СоврагеТо( (Совр1ехиитьег) оТЬег ) ( ) риыхо кпе Соврагето( Сшвр1ехнивьег ТЬае ) ( кпг геви1Т; 11( Ес(ив1в (Тиас) ) ( теви1Т = О' ) е1яе 11( ТЫв.Мвдпксибе > ТЬвТ.Мадпкгибе ) ( теви1Т = 1; ) е1ве ( теви1Т = -1; ) гетигп геви1Т; риЬ11с боиЬ1е Мвдпксибе ( оес ( гетитп Мвгь.ас(гт( МаТЬ Рои(тыся.гев1, 2) МатЬ.Рои(ТЬкв.кщвцкпагу, 2) ); ) !/ Прочие методы опущены для ясности.
рткчвте тевбоп1у боиЬ1е тев1; ргбнвсе гевбоп1у боиЫе Тщадкпвгу; ) риЫТс вев1еб с1ввв ЕптгуРокпт ( втвТбс чо1б Мвтп () ( Сощр1ехнищЬет пищ1 = пеи Сощр1ехнищпет( 1, 2 )к Сощр1ехнищЪег пищ2 = пеи Сотр1ехнитЬег( 1, 2 ); Ьоо1 геви1Т = пшп1.Ес(ив1в( пищ2 ); Теперь сравнение внутри Мвап стало более эффективным, поскольку отпала необходимость в упаковке значений. Компилятор выбирает наиболее подходяшую нз двух перегрузок, которой, конечно же, является строго типизированная перегрузка ес(иа1в, принимающая Сощр1ехншвЬет вместо обобщенного типа объекта. Внутренне ОЬчесТ.Ес(ив1в переопределяет делегаты в безопасную к типам версию Ес(иа1в после того, как проверит тип объекта и распакует его. Важно отметить, что перед распаковкой.














