Плохая практика? Неканоничное использование оператора using в c #

В C # есть оператор using, специально для объектов IDisposable. Предположительно, любой объект, указанный в операторе using, будет содержать какой-то ресурс, который должен быть освобожден детерминированно.

Однако мне кажется, что в программировании есть множество дизайнов, которые имеют одно определенное начало и конец, но не имеют встроенной языковой поддержки. Конструкция using предоставляет возможность использовать встроенные функции редактора кода, чтобы, по крайней мере, четко и естественно выделить объем такой конструкции или операции.

Я имею в виду операцию, которая часто начинается с методов BeginXXX() и EndXXX(), хотя существует множество различных вариантов, например выполнение асинхронного кода, которое включает «начало» и «соединение».

Возьмите этот наивный пример.

webDataOperation.Start();
GetContentFromHardDrive();
webDataOperation.Join();
// Perform operation that requires data from both sources

Что, если бы вместо этого метод Start вернул объект, IDisposable.Dispose метод которого выполняет операцию соединения.

using(webDataOperation.Start()) {
    GetContentFromHardDrive();
}
// Perform operation that requires data from both sources

Или, еще лучше, то, что я имел в виду: у меня есть объект, который выполняет узкоспециализированное копирование графики и имеет методы Begin() и End() (дизайн также присутствует в DirectX и XNA). Вместо...

using(blitter.BlitOperation()) {
    // Do work
}
// Use result

Это кажется более естественным и читаемым, но нецелесообразно ли это, учитывая, что он использует интерфейс IDisposable и оператор using для непреднамеренных целей? Другими словами, будет ли это наравне с o загрузкой оператора неинтуитивным способом?


person snarf    schedule 07.07.2009    source источник
comment
Почему бы просто не использовать {в начале и} в конце? Это обеспечивает область действия без злоупотребления использованием.   -  person cja    schedule 08.12.2014


Ответы (6)


Это вполне приемлемая практика. Они называются типами с факторизацией, а Framework Design Guidelines рекомендует именно это.

По сути, если тип завершает операцию с определенным временем жизни, использование IDisposable и оператора using становится подходящей вещью, которую следует учитывать.

На самом деле я тоже писал здесь об этой конкретной теме.

person Reed Copsey    schedule 07.07.2009
comment
Хорошая находка, но есть ли у вас аргументы в пользу своего аргумента? В статье это рекомендуется, но не обсуждается, почему. - person snarf; 08.07.2009
comment
Это потому, что он гарантирует, что EndOperation () всегда вызывается (через Dispose ()) - person Matthew Scharley; 08.07.2009
comment
@Snarfblam: По сути, сама операция действует как ресурс, который требует очистки. Подробнее читайте в моем блоге - это вторая ссылка. Я вложил в это гораздо больше оправданий, чем здесь. - person Reed Copsey; 08.07.2009
comment
также обратите внимание, что использование НЕ предназначено специально для объектов IDisposable, см. мою статью о дженериках с псевдонимами ebersys.blogspot.com/2006/08/hiding-generics-complexity.html - person BlackTigerX; 05.08.2009
comment
Это обсуждение касается оператора using, а не директивы using (т.е. импорт пространств имен и определение псевдонимов не имеют отношения к теме). - person snarf; 08.08.2009

Я не рекомендую этого; Я считаю, что код должен эффективно взаимодействовать с сопровождающим кода, а не с компилятором, и должен быть написан с учетом понимания сопровождающего. Я пытаюсь использовать «использование» только для удаления ресурса, обычно неуправляемого.

Я в меньшинстве. Большинство людей, кажется, используют «использование» в качестве механизма общего назначения «Я хочу, чтобы какой-то код очистки запускался, даже если было сгенерировано исключение».

Мне это не нравится, потому что (1) у нас уже есть механизм для этого, называемый "try-finally", (2) он использует функцию для цели, для которой она не предназначена, и (3) если вызов кода очистки выполняется важно, тогда почему он не виден в том месте, где он вызван? Если это важно, я хочу увидеть это.

person Eric Lippert    schedule 08.07.2009
comment
Мне кажется, что это отношение к using излишне ограничительно. Из любопытства, есть ли в C # какая-либо другая языковая функция, в которой вы бы не рекомендовали ее использование - даже если она работает идеально и четко - потому что указанное использование не было тем, для чего оно было разработано? - person Kirk Woll; 25.04.2012
comment
@KirkWoll: Да. Я не рекомендую использовать все языковые функции, которые используются для чего-то другого, кроме того, для чего они были разработаны. - person Eric Lippert; 25.04.2012
comment
@EricLippert - конечно, не всегда понятно, для чего была разработана языковая функция. Фактически, даже в конкретном случае using, я подумал, что есть некоторые признаки того, что имя using было специально выбрано, потому что ожидалось, что функция не будет использоваться только для удаления неуправляемых ресурсов. - person kvb; 25.04.2012
comment
Я лично не могу принять это убеждение, потому что в противном случае было бы против вашего убеждения использовать Java в любых других системах, кроме встроенных. - person rwong; 03.01.2015

Просто потому, что ты можешь (или потому, что Фил Хаак сказал, что это нормально), не означает, что ты должен.

Основное практическое правило: если я могу прочитать ваш код и понять, что он делает и каковы были ваши намерения, то это приемлемо. Если, с другой стороны, вам нужно объяснить, что вы сделали или почему вы это сделали, это, вероятно, сбивает с толку младших разработчиков, поддерживающих код.

Есть много других шаблонов, которые позволяют добиться этого с помощью лучшей инкапсуляции.

Итог: эта «техника» ничего не покупает и только сбивает с толку других разработчиков.

person Ryan Emerle    schedule 08.07.2009
comment
+1 за благодарность за понимание. Я полностью согласен - хотя, в целом, я думаю, что использование IDisposable для факторизованных типов обеспечивает очень чистые и понятные средства их обработки во многих случаях ... но это вопрос правильного написания вашего кода, а также очень очевидного того, что ты делаешь. то есть: TransactionScope в BCL для меня естественен - ​​кода отступа в другом ответе здесь нет. Как и многие другие вещи, я считаю, что это полезно, но им легко злоупотребить. - person Reed Copsey; 08.07.2009
comment
Согласованный. Что работает, работает. Легко увлечься интересными концепциями, но в реальном проекте нельзя использовать ничего, что только сбивает с толку. - person Charles; 08.07.2009
comment
Если бы меня это сбивало с толку, я бы не стал спрашивать. Не обращая внимания на потенциальную путаницу, есть ли у вас альтернатива, которая была бы проще или проще? Мне нравится, как использование является встроенным, с отступом и поддерживается языком, а также явно, конкретно и ясно инкапсулирует область видимости. - person snarf; 08.07.2009
comment
@Snarfblam: в некоторых случаях использование метода, который принимает делегат (некоторые действия / функции некоторой формы), и вызов этого между вашим кодом запуска / разрыва работает хорошо. Однако есть некоторые случаи, которые не решаются этим подходом. Подробности см. В ответе Павла Минаева. - person Reed Copsey; 08.07.2009

Это распространенный шаблон, но лично я считаю, что нет оправдания такому злоупотреблению IDisposable, когда вы можете достичь того же эффекта гораздо более очевидным способом с помощью анонимных делегатов и / или лямбда-выражений; то есть:

blitter.BlitOperation(delegate
{
   // your code
});
person Pavel Minaev    schedule 08.07.2009
comment
И, если вы хотите, скажем, всегда выполнять работу в другом потоке, вам не нужно разрывать свой код (т.е. этот дизайн добавляет ценность). - person Ryan Emerle; 08.07.2009
comment
Хотя мне тоже нравится этот шаблон, он добавляет своей сложности - в некоторых случаях он может стать менее очевидным, чем обработка с помощью оператора using. Многие (особенно начального уровня) разработчики испытывают проблемы с поиском лямбда-выражений. - person Reed Copsey; 08.07.2009
comment
В некоторых случаях это также не обеспечивает равноценной безопасности. Например, вы не можете использовать это внутри метода с оператором yield и получить гарантированную очистку ... - person Reed Copsey; 08.07.2009
comment
Рид, я не понимаю, что ты имеешь в виду. Если тело BlitOperation выполняет очистку после вызова делегата, почему это имеет значение, если вызывающий объект является методом итератора? - person Pavel Minaev; 08.07.2009

Я думаю, вам следует использовать IDisposable для того, для чего он предназначен, и ничего больше. То есть, если для вас важна ремонтопригодность.

person John Saunders    schedule 07.07.2009
comment
Джон: См. Рекомендации по разработке фреймворка для типов с факторизацией: blogs.msdn.com/brada/archive/2009/02/23/ - теперь это рекомендуемый подход к их устранению. - person Reed Copsey; 08.07.2009
comment
Как это влияет на ремонтопригодность? И это единственная причина, по которой вы рекомендуете строгое соблюдение канона? - person snarf; 08.07.2009
comment
Это влияет на способность других разработчиков понимать, что происходит. Моя причина для рекомендации состоит в том, что достаточно сложно заставить людей использовать IDisposable по обычным причинам - нам не нужны дополнительные. - person John Saunders; 08.07.2009
comment
Я согласен - я не использую (и не предлагаю) использовать его везде. При этом я думаю, что для него есть хорошие варианты использования, особенно когда вам нужно вызвать закрывающий аргумент или есть побочные эффекты. Я считаю, что именно поэтому он сейчас явно упоминается в рекомендациях по дизайну и часто используется в BCL таким образом. ИМО, как только он используется в нескольких местах в BCL и включен в официальные руководящие принципы проектирования, он становится нормальной причиной его использования. - person Reed Copsey; 08.07.2009

Я бы сказал, что это приемлемо - на самом деле, я использовал его в некоторых проектах, где хотел, чтобы действие запускалось в конце определенного блока кода.

Вес Дейер использовал его в своей программе LINQ to ASCII Art, он назвал это одноразовым действием (Уэс работает в команде компиляторов C # - я бы доверял его мнению: D):

http://blogs.msdn.com/wesdyer/archive/2007/02/23/linq-to-ascii-art.aspx

class ActionDisposable: IDisposable
{
    Action action;

    public ActionDisposable(Action action)
    {
        this.action = action;
    }

    #region IDisposable Members

    public void Dispose()
    {
        this.action();
    }

    #endregion
}

Теперь вы можете вернуть это из функции и сделать что-то вроде этого:

using(ExtendedConsoleWriter.Indent())
{
     ExtendedConsoleWriter.Write("This is more indented");
}

ExtendedConsoleWriter.Write("This is less indented");
person Charles    schedule 07.07.2009
comment
Вау, это вопиющее злоупотребление выражением using. Я бы понятия не имел, что пытался сделать этот код, не открывая код ExtendedConsoleWriter. Если бы вокруг кода были вызовы IncreaseIndent и DecreaseIndent, не было бы никаких вопросов. - person Ryan Emerle; 08.07.2009
comment
Я бы не стал использовать для этого using, но предположим, что существует аналогичное ключевое слово и связанный интерфейс, например with: with (writer.Indent ()) {stuff; }, это была бы убедительная конструкция. - person snarf; 08.07.2009
comment
Ой, Райан. Это был быстро написанный пример. Я согласен, я бы назвал это IncreaseIndent - в этом случае я бы не назвал это вопиющим злоупотреблением оператором using. Такого рода концепцию можно увидеть в области видимости транзакций в .NET - кажется, вы немного резко опускаетесь по мелочам. - person Charles; 08.07.2009