Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 57
Текст из файла (страница 57)
чтобы их можно было легко отличить друг от друга. Зта цель частично была утеряна иэ-за того, что некоторые исключения, определенные в .)ч)ЕТ Ргакпеюог)г, наследуются от АРР11саг).опЕхсергзоп . Подробнее об этом, наряду со многими другими полезными рекомендациями, написано в руководстве кжиштова Цвалина (кгяувзго1 сна)ша) н Брэда Абрамса (Вгад Аьгать) Ргатяшог)г Пеядп Оии(еИпеь: Согшеппопь, )г((отв, аги( Рааегпв )ог йеияаЫе .)ЧЕТ Р(Ьгапея, 2пг( ЕгШ(оп (Адд(ьоп-Жев)еу Ргогевюопа), 2008 г).
Планируется к выходу. Разработка инфраструктуры проектирования: соглашения, идиомы и шаблоны (ИД "Вильямс", 2010 г). 2!4 Глава 7 Определение целых четырех конструкторов для такого простого типа исключений может удивить. Дело в там, что при определении новых типов исключений действует традиционная идиома, требующая определения тех же самых четырех общедоступных конструкторов, которые есть в ьузсеа.
Ехсерс1оп. Если бы не планировалось передавать дополнительные данные о причине исключения, то конструкторы евр1оуеечег111сас1опехсерс1оп в точности соответствовали по форме конструкторам Яузгев.Ехсерг1оп. Если вы последуете этой идиоме при создании собственных типов исключений. то пользователи смогут трактовать новый тип исключения точно таким же образом, как любое другое определенное системой исключение.
Вдобавок унаследованное исключение сможет использовать сообщение и внутреннее исключение, которые ужеинкапсулированы в Яузпеа.Ехсер11оп. Работа с выделенными ресурсами и исключениями Если вы обладаете большим опытом работы в С++, то одна вещь в мире СЗ покажется наиболее захватывающей — отсутствие детерминированной деструкции.
Разработчики С++ привыкли использовать конструкторы и деструкторы объектов, располагаемых в стеке, для управления ценными ресурсами. Эта идиома даже имеет собственное название — ВАВ (йезоигсе АсцшзИоп 1з 1пШайзпбоп — захват ресурсов является инициализацией). Это означает возможность создания объектов в стеке С++, в которых некоторый ценный ресурс выделяется в конструкторе, и если он освобождается в деструкторе, то можно положиться на его автоматический вызов для очистки в нужное время.
Например, независимо от того, каким образом объекты, расположенные в стеке, покидают область видимости — через нормальное выполнение при достижении конца контекста либо по исключению — деструктор будет гарантированно выполнен, освободив при этом ценный ресурс. Когда СФ и СЬК впервые были представлены разработчикам в виде бета-версии, многие немедленно подняли шум об этом недостатне исполняющей системы. Считаете вы зто недостатком или нет, но оно точно не касается наиболее полного расширения, которое появилось после замечаний от сообщества разработчиков, использующих бетаверсию.
Проблема происходила отчасти из природы объектов СЬК, которыми заведует сборщик мусора, наряду с тем фантом. что дружественный деструктор в синтаксисе Са был повторно использован для реализации финализаторов объекта. Важно также помнить, что финализаторы существенно отличаются от деструкторов. Применение синтаксиса деструктора для финализаторов только добавило путаницы. Были также и другие технические причины, часть которых касалась эффективности, из-за чего детерминированные деструкторы, нак мы их знаем, не были включены в исполняющую систему.
После некоторых размышлений было предложено решение в виде шаблона 1МзрозаЫе, предусматривающего реализацию интерфейса 101зрозаЬ1е. Более детальную информацию относительно шаблона П!зрозаЫе и объектов можно найти в главах 4 и 13. По сути, если объект нуждается в детерминированной деструкции, он получает ее за счет реализации интерфейса 1П1зроз аЬ1е. Однако метод 01зрозе должен быть вызван явно, чтобы выполнить "уборку" за одноразовым объектом. Если вы забудете это сделать, а объект аакодирован правильно, то ресурс не будет утерян — он будет просто очищен тогда, когда сборщик мусора, наконец, вызовет финализатор. Внутри С++ нужно только не забыть поместить код очистки в деструктор, после чего никогда не придется заботиться об очистке локальных объектов, поскольку такая очистка происходит автоматичесни при выходе из области определения.
Безопасность и обработка исключений 216 Рассмотрим следующий надуманный пример, иллюстрирующий опасность, с которой можно столкнуться: пвгпд Яувгев) пягпд Яувгеы.ТО; пвгпд яувсеы.теис; рпЬЬгс с1авв ЕпсгуГогпс ( рпЬ1гс всаггс то1б Оояотезгигг О ( // Открыть файл. Г11еБГгевю Гв = Г11е.ореп( "1од.гхс", Гг1енобе.Аррепб, Г11елссевв.Хггге, Г11еЯПаге.ноле ); ВугЕ[) Ыяд = ПЕИ ОТГ8ЕПСОб1Пд(ГГПЕ).ОЕГВуГЕВ("ООТПд ЯптЕчт Ягогг")к Гв.итаке( тяд, О, ияд.Ьепдси ); ) роЬ11с ясаг1с то1б ОояоыеиогеБСигг О ( !/ Открыть файл. Г11еясгеат Гв = Г11е.ореп( "1од.схг", Гг1еМобе.лррепб, Г11елссевя Хггсе Г11еяивге.ноле ); Вуге[) ряд = пеы ОТГ8Епсоб1пд(сгпе).оесвусея("Оо1пд Яоые"ь " Моте Бгпгг")) Гв.иг1ге( твд, О, тяд.Ьепдгп ); ) всякгс чогб Мз1п() ( Оозотеяспгс()к ОоЯатеМогеясоГГ О; Этот код выглядит вполне невинно.
Однако в результате его запуска почти наверняка возникнет исключение 10ехсерг1оп. Код в ООБотеяспТТ создает объект Г11еясгеат с монопольной блокировкой файла. Как только объект Г11еЯГгеат выходит из контекста в конце функции, он помечается для уборки, но когда именно она произойдет. приходится полагаться на сборщик мусора [ОС). Таким образом, во время следующей попытки открытия файла в ООБоыеногеЯГп ГТ генерируется исключение, поскольку ценный ресурс все еще заблокирован недоступным объектом Г11еБСгеась Ясно, что это весьма неприятная ситуация. Даже не думайте о явном вызове ОО .
Оо11есо в Мвз.п перед вызовом ОояоюемогеБспТГ. Попытка управлять алгоритмом работы СО, чтобы заставить его подбирать объекты в определенное время — путь к снижению производительности. Вы не сможете помочь ОС лучше выполнять его работу, поскольку не имеете представления о том, как он реализован. Как же поступить? Так или иначе, необходимо гарантировать закрытие файла. Однако здесь есть шероховатость: независимо от того, как это делается, нужно помнить о том, что это должно быть сделано.
В этом состоит отличие от языка С++, где можно поместить код очистки в деструктор и потом просто быть уверенным, что ресурс будет очищен в надлежащий момент. Одним из вариантов может быть вызов метода О1ояе объекта Гз.1еБ с гевю в каждом методе, использующем его. Это работает хорошо, но степень автоматизации ниже, и всегда необходимо помнить, что это нужно сделать. Однако даже если не забыть это сделать, то что произоцдет.
если до вызова метода О1ояе сге- 216 Глава 7 нерируегся исключением Вы окажетесь в той же ситуации, что и раньше, с утерянным ресурсом, к которому невозможно обратиться даже для его освобождения. Те, кто знаком с обработкой исключений, отметят, что описанную выше проблему можно решить с использованием блоков сгу/Тфпа11у, как показано в следующем примере: пв1пд Яувгехс из1пд Яуягек.10) пвспд Яузгеа.техг; роо11с с1авв ЕпггуРо1пС ( рпЫТс яСаг1с нога Оояокеягпсг() ( // Открыть файл. Г11еЯСгеаа Тя = пп11) сгу ( Гя = Г11е.орел( "1од.гхг", Г11еМос(е.лррепс(, Г11еАссевв.нг1Се, Г11еяьаге.коле ); Буге() квд = ПЕя ОТГ8ЕПСОО1од(СГСЕ).СЕСВуСЕя("ООЬПЯ БОтЕат " Ясо111п"); Тв.кгТСе( авд, О, твд.ЬепдСП ); 11па11у ( 11( Гв != пп11 ) ( Гя.С1ове(); ) ) рсЫ1с ягаС1с нояб Оояокеногеягост() ( // Открыть файл.
Г11еясгеав Тз = пс11; сгу ( Гв = Г).1е.ореп( "1од,гхг", Г11еМопе.дррепс), Г11еАссеяв.кг1се, Г11еяьаге.копе ); ВуСе() аяд = ПЕЫ ОТГ8ЕПСООТПВ(СГСЕ).ОЕСВуСЕВ("ОО1сд ЯООЕат " Моте Ягсг11п") Гя.нгфге( квд, О, ряд.ьепдгь ); ) Тспа11у ( 11( Тв != пп11 ) ( Тя.С1ове()с ) ) вгаггс нофа Мавп() ( ОояонеЯСпТГ(); ОоаоаеногезгсГТ(); ) Безопасность и обработка исключений 217 Блоки Г ту/ Еьпа11у решают проблему. Но насколько некрасивым стал код! Вдобавок давайте признаем, то многие из нас довольно ленивы, а здесь — огромный объем дополнительного клавиатурного набора.
Более того, больше текста означает больше возможностей внести ошибку. И, наконец, это затрудняет чтение кода. Как и можно было ожидать, существует путь получше. Многие объекты, такие как Р11еяггевы, имеющие метод С1ове, также реализуют шаблон В)вроваЫе. Обычно вызов ОЕярояе на этих объектах — это то же самое, что вызов С1ояе. Вызов С1ове через Огярояе нли наоборот— это спор о разных вкусах, если все равно нужно явно вызывать тот или другой метод.
К счастью, есть хорошая причина того, почему большинство классов. имеющих метод С1ояе, реализуют Оаврояе — появляется возможность использовать их в операторе ия1пд, который обычно применяется как часть шаблона [Т(вроваЫе в Си. Поэтому следует изменить код, как показано ниже: пвапд Яуясекы пягпд Яувсеы.ТО) пяапд Яувсеы.теис) РпЬ11с с1ввв ЕпсгУРозпс ( рпЬ11с япвкас то16 ОоЯовевсиЕЕ() ( // Открыть файл. пв1пд( Р11евсгеят Ев = Р11е.орел( "1од.схг", Р11еМос(е.Аррепс), Р11еАссевя.нгЕГе, Р11еЯЬяге.нове ) ) ( Вусе[) ряд = пеи ОТР8Епсосапд(кгпв).БеГВукея("Оо1пд Боте" БспЕЕ1п") ( Ев.итаке ( твд, О, твд.ЬепдГЬ ) ( ) ) роЬ11с ясяс1с то!О ОоЯоыеногеЯГпЕЕ() // Открыть файл. ия1пд( Р11еЯГгеат Ея = Р11е.орел( "1од.схс", Р11еноое.лррепо, Р11елссеяв.кггте, Р11еБЬаге.ноле ) ) Вусе[) ряд = пеи ОТР8Епсос(1пд(ягпе).БеГВусея("Оо1пд Боне" " Моте ЯсиЕЕ1п"); Ев.нгЕге( ыяд, О, ыяд.ьепдГЬ )! ) ) ясяГЕс тоаб МаЕп() ( ОоЯоп1ЕБГпЕЕ()( ОовоыеногевспЕЕ(); Как видите, этот код гораздо легче понять,и оператор ояапд позаботится обо всем, что связано с блоками Ггу/Е1па11у.
Возможно, вы не удивитесь, просмотрев сгенерированный код в ИСАЯМ и увидев там, что компилятор генерирует блоки ггу/Еапа11у вместо оператора пя1пд. Можно даже вкладывать конструкции пя).пд одну внутрь другой — точно так же, как это можно делать с блоками г ту/ Езпа11у. Но даже несмотря на то, что оператор ия !. пд устраняет симптом некрасиво выглядящего кода и сокращает шансы появления дополнительных ошибок, он все-таки требует 218 Глава 7 помнить о необходимости его применения. Это не так удобно, как детерминированная деструкция локальных объектов в С++, но это лучше, чем оснащение кода блоками г ту/ Гапа11у.














