Что-то, что я часто использовал в C ++, позволял классу A
обрабатывать условие входа и выхода из состояния для другого класса B
через A
конструктор и деструктор, чтобы убедиться, что если что-то в этой области выдает исключение, тогда B будет иметь известное состояние, когда область была закрыта. С точки зрения аббревиатуры, это не чистый RAII, но, тем не менее, это устоявшийся шаблон.
В C # я часто хочу сделать
class FrobbleManager
{
...
private void FiddleTheFrobble()
{
this.Frobble.Unlock();
Foo(); // Can throw
this.Frobble.Fiddle(); // Can throw
Bar(); // Can throw
this.Frobble.Lock();
}
}
Что нужно сделать вот так
private void FiddleTheFrobble()
{
this.Frobble.Unlock();
try
{
Foo(); // Can throw
this.Frobble.Fiddle(); // Can throw
Bar(); // Can throw
}
finally
{
this.Frobble.Lock();
}
}
если я хочу гарантировать Frobble
состояние при FiddleTheFrobble
возврате. Код будет лучше с
private void FiddleTheFrobble()
{
using (var janitor = new FrobbleJanitor(this.Frobble))
{
Foo(); // Can throw
this.Frobble.Fiddle(); // Can throw
Bar(); // Can throw
}
}
где FrobbleJanitor
выглядит примерно как
class FrobbleJanitor : IDisposable
{
private Frobble frobble;
public FrobbleJanitor(Frobble frobble)
{
this.frobble = frobble;
this.frobble.Unlock();
}
public void Dispose()
{
this.frobble.Lock();
}
}
И вот как я хочу это сделать. Теперь реальность догоняет, так что я хочу , чтобы использование требует , что FrobbleJanitor
используется с using
. Я мог бы считать это проблемой проверки кода, но что-то меня не покидает.
Вопрос: Будет ли вышеизложенное считаться неправомерным использованием using
и IDisposable
?
источник
Ответы:
Не думаю, что обязательно. IDisposable технически это предназначается , чтобы быть использована для вещей , которые неуправляемые ресурсы, но затем с помощью директивы является просто аккуратным способом реализации общей картины
try .. finally { dispose }
.Пурист возразит: «Да, это оскорбительно», и в пуристическом смысле это так; но большинство из нас пишут код не с пуристской точки зрения, а с полухудожественной. На мой взгляд, использование конструкции «использование» таким образом действительно довольно артистично.
Вам, вероятно, следует прикрепить другой интерфейс поверх IDisposable, чтобы отодвинуть его немного дальше, объясняя другим разработчикам, почему этот интерфейс подразумевает IDisposable.
Есть много других альтернатив для этого, но, в конечном счете, я не могу придумать ничего, что было бы столь же аккуратно, как это, так что дерзайте!
источник
using(Html.BeginForm())
сэр? не против, если я так сделаю, сэр! :)Я считаю это злоупотреблением оператором using. Знаю, что на этой должности я в меньшинстве.
Я считаю это злоупотреблением по трем причинам.
Во-первых, потому что я ожидаю, что "using" используется для использования ресурса и удаления его, когда вы закончите с ним . Изменение государственной программы не используя ресурс и изменить его обратно не распоряжаться ничего. Следовательно, «использование» для изменения и восстановления состояния является злоупотреблением; код вводит в заблуждение случайного читателя.
Во-вторых, потому что я ожидаю, что слово «использование» будет использоваться из вежливости, а не по необходимости . Причина, по которой вы используете "using" для удаления файла, когда вы закончите с ним, не потому, что это необходимо сделать, а потому, что это вежливо - кто-то другой может ждать, чтобы использовать этот файл, поэтому сказал "готово сейчас "- это нравственно правильный поступок. Я ожидаю, что я смогу провести рефакторинг «using», чтобы использованный ресурс удерживался дольше и утилизировался позже, и что единственное влияние этого - небольшое неудобство для других процессов . Блок "использования", который имеет семантическое влияние на состояние программы. является оскорбительным, потому что он скрывает важную, необходимую мутацию состояния программы в конструкции, которая выглядит так, как будто она существует для удобства и вежливости, а не по необходимости.
И в-третьих, действия вашей программы определяются ее состоянием; необходимость осторожного манипулирования состоянием - вот почему мы впервые ведем этот разговор. Давайте рассмотрим, как мы можем проанализировать вашу исходную программу.
Если бы вы принесли это на проверку кода в моем офисе, первый вопрос, который я бы задала, будет: «Действительно ли правильно блокировать блокировку, если возникает исключение?» Из вашей программы явно очевидно, что эта штука агрессивно повторно блокирует блокировку, что бы ни случилось. Это правильно? Возникло исключение. Программа находится в неизвестном состоянии. Мы не знаем, выбросили ли Foo, Fiddle или Bar, почему они выбросили или какие мутации они произвели в другом состоянии, которое не было очищено. Сможете ли вы убедить меня, что в этой ужасной ситуации всегда будет правильным сделать повторную блокировку?
Может быть, а может и нет. Я хочу сказать, что с кодом, который был изначально написан, рецензент знает, что нужно задать вопрос . С кодом, который использует "using", я не знаю, как задать вопрос; Я предполагаю, что блок «using» выделяет ресурс, использует его в течение некоторого времени и вежливо избавляется от него, когда это будет сделано, а не то, что закрывающая скобка блока «using» изменяет состояние моей программы в исключительных случаях, когда произвольно много нарушены условия согласованности состояния программы.
Использование блока «using» для семантического эффекта делает этот фрагмент программы:
чрезвычайно значимый. Когда я смотрю на эту единственную закрывающую скобу, я не сразу думаю, что «эта скобка имеет побочные эффекты, которые имеют далеко идущие последствия для глобального состояния моей программы». Но когда вы так злоупотребляете «использованием», внезапно это происходит.
Второе, что я бы спросил, если бы увидел ваш исходный код, это «что произойдет, если после разблокировки, но до того, как будет введена попытка, будет сгенерировано исключение?» Если вы запускаете неоптимизированную сборку, компилятор мог вставить инструкцию no-op перед попыткой, и может произойти исключение прерывания потока на no-op. Это редко, но в реальной жизни случается, особенно на веб-серверах. В этом случае разблокировка происходит, но блокировка никогда не происходит, потому что исключение было сгенерировано до попытки. Вполне возможно, что этот код уязвим для этой проблемы и действительно должен быть написан
Опять же, может быть, это так, может быть, нет, но я знаю, что задать вопрос . С версией "using" он подвержен той же проблеме: исключение прерывания потока может быть сгенерировано после того, как Frobble заблокирован, но до того, как будет введена область с защитой от попытки, связанная с использованием. Но с версией "с использованием" я предполагаю, что это "ну и что?" ситуация. Очень жаль, если это произойдет, но я предполагаю, что «использование» используется только для вежливости, а не для изменения жизненно важного состояния программы. Я предполагаю, что если какое-то ужасное исключение прерывания потока произойдет точно в неподходящее время, тогда, ну что ж, сборщик мусора в конечном итоге очистит этот ресурс, запустив финализатор.
источник
using
это вопрос вежливости, а не правильности. Я считаю, что утечки ресурсов - это проблемы с правильностью, хотя часто они настолько незначительны, что не являются высокоприоритетными ошибками. Таким образом,using
блоки уже оказывают семантическое влияние на состояние программы, и то, что они предотвращают, - это не просто «неудобство» для других процессов, но, возможно, нарушенная среда. Это не означает, что другой тип семантического воздействия, подразумеваемый вопросом, также уместен.using
тем, что это аспектно-ориентированный ткач для бедняков на C #. Что действительно нужно разработчику, так это способ гарантировать, что какой-то общий код будет выполняться в конце блока без необходимости дублировать этот код. C # сегодня не поддерживает АОП (за исключением MarshalByRefObject и PostSharp). Однако преобразование компилятором оператора using позволяет достичь простого АОП за счет изменения семантики детерминированного распределения ресурсов. Хотя это и не является хорошей практикой, иногдаusing
слишком привлекательна, чтобы отказываться от нее . Лучший пример, который я могу придумать, - это отслеживать и сообщать о времени выполнения кода:using( TimingTrace.Start("MyMethod") ) { /* code */ }
опять же, это АОП -Start()
фиксирует время начала до начала блока,Dispose()
фиксирует время окончания и регистрирует активность. Он не меняет состояние программы и не зависит от исключений. Он привлекателен еще и тем, что позволяет избежать использования сантехники, а частое использование в качестве шаблона смягчает запутанную семантику.Если вам просто нужен чистый код с ограниченной областью видимости, вы также можете использовать лямбды, á la
где
.SafeExecute(Action fribbleAction)
метод оборачивает блокtry
-catch
-finally
.источник
SafeExecute
этоFribble
метод, который вызываетUnlock()
иLock()
в завернутом try / finally. Тогда мои извинения. Я сразу увиделSafeExecute
общий метод расширения и поэтому упомянул об отсутствии состояния входа и выхода. Виноват.Эрик Ганнерсон, работавший в команде разработчиков языка C #, ответил примерно на тот же вопрос:
источник
using
функция не должна ограничиваться утилизацией ресурсов.Это скользкий спуск. У IDisposable есть контракт, поддерживаемый финализатором. В вашем случае финализатор бесполезен. Вы не можете заставить клиента использовать оператор using, только поощряйте его это делать. Вы можете заставить его использовать такой метод:
Но без ламдов это становится немного неловко. Тем не менее, я использовал IDisposable, как и вы.
Однако в вашем сообщении есть деталь, которая делает это опасно близким к антипаттерну. Вы упомянули, что эти методы могут вызывать исключение. Это не то, что вызывающий абонент может игнорировать. Он может сделать с этим три вещи:
Последние два требуют, чтобы вызывающий объект явно написал блок попытки. Теперь мешает инструкция using. Это вполне может ввести клиента в кому, что заставит его поверить, что ваш класс заботится о состоянии и не требует дополнительной работы. Это почти никогда не бывает точным.
источник
SafeExecute
как общий метод расширения. Теперь я понимаю, что это, вероятно, имелось в виду какFribble
метод, который вызываетUnlock()
иLock()
в завернутом try / finally. Виноват.Реальным примером является BeginForm ASP.net MVC. В основном вы можете написать:
или
Html.EndForm вызывает Dispose, а Dispose просто выводит
</form>
тег. Хорошая вещь в этом заключается в том, что скобки {} создают видимую «область видимости», которая упрощает просмотр того, что находится внутри формы, а что нет.Я бы не стал злоупотреблять им, но, по сути, IDisposable - это просто способ сказать: «Вы ДОЛЖНЫ вызвать эту функцию, когда закончите с ней». MvcForm использует его, чтобы убедиться, что форма закрыта, Stream использует его, чтобы убедиться, что поток закрыт, вы можете использовать его, чтобы убедиться, что объект разблокирован.
Лично я бы использовал его только в том случае, если следующие два правила верны, но они установлены мной произвольно:
В конце концов, все дело в ожиданиях. Если объект реализует IDisposable, я предполагаю, что ему нужно выполнить некоторую очистку, поэтому я называю это. Я думаю, что это обычно лучше, чем наличие функции «Завершение работы».
При этом мне не нравится эта строка:
Поскольку FrobbleJanitor теперь "владеет" Frobble, мне интересно, не лучше ли было бы вместо этого вызвать Fiddle на Frobble в Janitor?
источник
У нас много использования этого шаблона в нашей кодовой базе, и я видел его повсюду раньше - я уверен, что это тоже должно было обсуждаться здесь. В общем, я не вижу, что в этом плохого, это дает полезный шаблон и не причиняет реального вреда.
источник
Подключаюсь к этому: я согласен с большинством присутствующих в том, что это хрупко, но полезно. Я хотел бы указать вам на класс System.Transaction.TransactionScope , который делает то, что вы хотите.
Вообще мне нравится синтаксис, он убирает много лишнего из реального мяса. Пожалуйста, подумайте о том, чтобы дать вспомогательному классу хорошее имя - возможно ... Scope, как в примере выше. Название должно указывать на то, что оно инкапсулирует фрагмент кода. * Scope, * Block или что-то подобное должно это делать.
источник
Примечание: моя точка зрения, вероятно, смещена из-за моего опыта работы с C ++, поэтому значение моего ответа следует оценивать с учетом этого возможного предубеждения ...
Что говорит спецификация языка C #?
Цитата из спецификации языка C # :
Код , который использует ресурс , конечно, код , начиная от
using
ключевого слова и идти до объема присоединенного кusing
.Думаю, это нормально, потому что Lock - это ресурс.
Возможно, ключевое слово
using
было выбрано неправильно. Возможно, его следовало назватьscoped
.Тогда мы можем рассматривать почти все как ресурс. Дескриптор файла. Сетевое подключение ... Нить?
Нить???
Используете (или злоупотребляете)
using
ключевым словом?Было бы здорово использовать (ab)
using
ключевое слово, чтобы убедиться, что работа потока завершена перед выходом из области видимости?Херб Саттер, кажется, считает это блестящим , поскольку он предлагает интересное использование шаблона IDispose для ожидания завершения работы потока:
http://www.drdobbs.com/go-parallel/article/showArticle.jhtml?articleID=225700095
Вот код, скопированный из статьи:
Хотя код C # для активного объекта не предоставляется, написано, что C # использует шаблон IDispose для кода, версия C ++ которого содержится в деструкторе. И, глядя на версию C ++, мы видим деструктор, который ожидает завершения внутреннего потока перед завершением, как показано в этом другом отрывке из статьи:
Так что, что касается Херба, он блестящий .
источник
Я считаю, что ответ на ваш вопрос отрицательный, это не будет злоупотреблением
IDisposable
.Как я понимаю
IDisposable
интерфейс, вы не должны работать с объектом после его удаления (за исключением того, что вам разрешено вызывать егоDispose
метод так часто, как вы хотите).Поскольку вы создаете новый объект
FrobbleJanitor
явно каждый раз, когда переходите кusing
оператору, вы никогда не используете один и тот жеFrobbeJanitor
объект дважды. И поскольку его цель - управлять другим объектом,Dispose
кажется подходящим для задачи по освобождению этого («управляемого») ресурса.(Кстати, стандартный пример кода, демонстрирующий правильную реализацию,
Dispose
почти всегда предполагает, что должны быть освобождены и управляемые ресурсы, а не только неуправляемые ресурсы, такие как дескрипторы файловой системы.)Единственное, о чем я лично беспокоюсь, это то, что менее ясно, что происходит с,
using (var janitor = new FrobbleJanitor())
чем с более явнымtry..finally
блоком, где операцииLock
&Unlock
непосредственно видны. Но выбор подхода, вероятно, зависит от личных предпочтений.источник
Это не оскорбительно. Вы используете их для того, для чего они созданы. Но вам, возможно, придется рассматривать друг друга в соответствии с вашими потребностями. Например, если вы выбираете «артистизм», вы можете использовать «using», но если ваш фрагмент кода выполняется много раз, то по соображениям производительности вы можете использовать конструкции «try» .. «finally». Потому что «использование» обычно подразумевает создание объекта.
источник
Я думаю, ты все сделал правильно. Перегрузка Dispose () была бы проблемой, когда тот же класс позже провел очистку, которую он действительно должен был выполнить, и время жизни этой очистки изменилось, чтобы отличаться от времени, когда вы ожидаете удерживать блокировку. Но поскольку вы создали отдельный класс (FrobbleJanitor), который отвечает только за блокировку и разблокировку Frobble, вещи достаточно разделены, вы не столкнетесь с этой проблемой.
Я бы переименовал FrobbleJanitor, вероятно, во что-то вроде FrobbleLockSession.
источник