Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 58
Текст из файла (страница 58)
и определенно это лучше, чем ничего. В результате мы получаем в С№ некоторую форму детерминированной деструкции через оператор ия1пс, но она остается детерминированной, только если не забыть ее сделать таковой. Обеспечение поведения отката Разрабатывая нейтральные к исключениям методы, как было описано в разделе "Обеспечение нейтральности к исключениям" настоящей главы, часто оказывается удобным механизм, который может выполнять откат любых изменений в случае генерации исключения.
Эту задачу можно решить, применив классическую технику ввода дополнительного промежуточного уровня в виде вспомогательного класса. Для иллюстрации давайте воспользуемся объектом, который представляет соединение с базой данных и содержит методы по имени Соппп1Г (фиксация) н ро11Ьас)с (откат). В мире С++ популярное решение этой проблемы подразумевает разработку вспомогательного класса, экземпляр которого создается в стеке.
Вспомогательный класс также имеет метод Сопппас. При его вызове он просто проходит через метод объекта базы данных, но перед этим выставляет внутренний флаг. Трюк кроется в деструкторе. Если деструктор выполняется перед тем, как флаг выставлен, существуют только две возможности. Первая — когда пользователь просто забыл вызвать Сопппфс. Поскольку это означает ошибку в коде, такую возможность рассматривать не будем.
Вторая возможность попасть в деструктор при не выставленном флаге — очистка объекта в процессе раскрутки стека в поисках обработчика исключения. В зависимости от состояния флага в коде деструктора определяется, что управление попало сюда в результате нормального выполнения нли же через исключение. Если через исключение, то все, что понадобится сделать — это вызвать Ко11ьасх на объекте базы данных, н получается необходимая функциональность. Все это прекрасно в мире С++, где можно воспользоваться детерминированной деструкцией.
Однако того же конечного результата можно достичь с применением формы детерминированной деструкции С№, представляющей собой сочетание 1РьярояаЬ1е и ключевого слова пя 1пс. Вспомните, что деструктор в "родном" С++ отображается на интерфейс 1пьярояаЬ1е в С№. Все, что потребуется сделать — взять код, который был бы помещен в деструктор в С++, и включить его в метод П1ярояе вспомогательного класса С№. Посмотрим, как может выглядеть такой вспомогательный класс С№: пяапч Яуясез; оя1по Яуягеи.шадпояс1сяв рпЬ11с с1аяя Вапаьаяе ( рпь11с поьб Созиьп () ( Сопяо1е.кгаге11пе( "Изменения зафиксированы" ) роь11с по1Н Ко11ьаск() ( Сопяо1е.нг1сеЬ1пе( "Изменения отменены" ) ) роЬ11с с1аяя Ко11ьаспне1рег: 1вьярояаЬ1е ( рпЬ11с Ко11ьаспяе1рег( Пагаьаяе оь ) ( ГЬ1я.п)Ь = б)и Безопасность и обработка исключений 219 -Но11ЬасИНе1рег() ( Неврозе( Еа1ве ) ) роЫгс поги Неврозе() Огврозе( Сгпе )к роЫгс ноги Сопюп1Г() ( с(ь.сопппгг () > соппп1ссеб = Ггпа> ) ргзпаге по1б Озерове( Ьоо1 бзвровзпд ) ( // Если объект уже освобопспен, то не делать ничего.
Помните, // что вызывать Оаврозе() несколько раз на одном объекте // совершенно законно. ЕЕ( !с(1зровес( ) ( базрозеб = огне; // Помните, что мы не хотим ничего делать с бЬ, // если попали сюда из финализатора, поскольку // поле базы данных может быть уже финализированным! 1С( бгвровзпд ) ( ге( (сопапгггеб ) ( бЬ.Ео11Ьас)с() ) ) ) е1зе ( ОеЬоо.аввегс( Еа1ве, "Сбой при вызове О1врозе()" + " на Но11ЬасИНе1рег" )с рггчасе Оагаоаве бЬ) ргзпасе Ьоо1 баврозеб = Са1ве) ргзчасе Ьоо1 сопшпгсед = Еа1ве) ) роЫзс с1авв ЕпггуРогпс ( зпасзс рггпасе чогб ОоЯошеиогк() ( оззпд( Но11Ьасиие1рег ооагб = лен Но11Ъаские1рег(бЬ) ) ( // Здесь выполняем некоторую работу, которая может // сгенерировать исключение.
// Удалите комментарий со следующей строки, // чтобы сгенерировать исключение: // по11рсг.оеГТуре()к // Если добрались сюда, фиксируем. соагб.сопип1с()к ) зсаггс поги Магп() ( бЪ = лен ОасаЬазе(); Ооаошеиог)с(); всасгс рггчасе ОагаЬаве бЬ; всасзс рггчасе ОЬфесг по11РГг = по11к 220 Глава 7 Внутри метода Роэовсеиогх выполняется работа, которая может дать сбой с выдачей исключения. Если сгенерируется исключение, необходимо отменить все изменения, проведенные в объекте РаеаЬаэе.
Внутри блока сзьод создается новый объект Но11ЬасХНе1ре г, содержащий ссылку на объект РаезЬа эе. Если поток управления достигает точки вызова Сопвс1с на ссылке ооагсЬ значит, все хорошо, если предположить, что метод Ровна" не сгенерирует исключений. Даже если это произойдет, он должен кодироваться таким образом, чтобы РагзЬаэе оставалась в корректном состоянии. Однако если код внутри защищенного блока сгенерирует исключение, то метод Разрезе на Но11ЬасхНе1ре г аккуратно выполнит откат базы данных. Независимо от того, что случилось, благодаря блоку оэ1пс1, метод Раэрозе будет вызван на экземпляре Но11Ьэсхпе1рег.
Если вы забудете применить блок иэ1пд, то финализатор Но11ЬасННе1рег не сможет ничего сделать, поскольку финализация объектов происходит в случайном порядке, и экземпляр РагаЬаэе, на который ссылается Но11ЬасННе1рег, может быть финэлизирован до экземпляра Но11ЬасхНе1рег. Чтобы помочь найти место, где допущена ошибка, можно закодировать утверждение (аээегбоп) во вспомогательном объекте, как зто было сделано выше. Поскольку применение всего шаблона зиждется на блоке из1нс1, для целей дальнейшего обсуждения предположим, что вы не забыли о нем. Как только поток управления благополучно достигнет метода Р1эроэе и попадет туда через явный вызов Разрезе, а не через финализатор, он просто проверит флаг фиксации, и если он не установлен, то вызовет Но11Ьаск на экземпляре РаеаЬаэе.
Вот и все, Получается почти столь же элегантно, как в решении на С++, за исключением того, что, как ранее говорилось в этой главе, нужно не забыть о применении ключевого слова и э с оо, чтобы все это работало. Если хотите посмотреть, что произойдет в случае генерации исключения, просто удалите комментарий со строки, где производится попытка обращения к нулевой ссылке внутри метода Роэовсеиогх. Возможно, вы заметили, что случай, когда Но11Ьас1с сгенерирует исключение, никак не учитывается. Ясно, что для устойчивости кода оптимально требовать, чтобы все операции, выполняемые Но11ЬасКНе1рег в процессе отката, были гарантированы от генерации исключений.
Это возвращает нас к одному из наиболее основополагающих требований для генерации строго безопасного к исключениям и нейтрального к исключениям кода: чтобы создать устойчивый, безопасный к исключениям код, необходимо иметь четко определенный набор операций, которые гарантированно не генерируют исключений. В мире С++ при раскрутке стека, вызванной исключением, откат происходит в деструкторе. Ветераны С++ знают, что в деструкторе никогда нельзя генерировать исключений, потому что стек находится в процессе раскрутки, и если сгенерировать исключение, раскрутка будет очень грубо прервана. А нет ничего хуже. чем исчезновение приложения без каких-либо следов в виде трассировки.
Но что, если такое случится в СЗ? Вспомните, что блок оз1оо разворачивается в конструкцию Сгу/г1ва11у. Когда исключение генерируется в блоке 11пз11у, который выполняется в результате предыдущего исключения, то предыдущее исключение просто теряется. Что еще хуже, так это то, что выполнявгпийся перед этим блок Ггса11у не может быть завершен. Поэтому с учетом того, что утеря информации об исключении — это всегда плохо и весьма затрудняет поиск причин проблемы, настоятельно рекомендуется никогда не генерировать исключения внутри блока 11яа11у.
Хотя об этом уже упоминалось ранее в главе, но никогда не будет лишним повторить. Если исключение будет сгенерировано во время выполнения блока Гага11у, то среда СЬЯ не прервет приложение, но приложение, скорее всего, окажется в неопределенном состоянии. Безопасность и обработка исключений 221 Резюме В этой главе были раскрыты основы обработки исключений, а также объяснялось применение шаблона Ехрегт для определения наилучшего места обработки конкретного исключения.












