Используя блок в изобилии?
Я хотел бы, чтобы ваше мнение по следующей теме:
Представьте, что у нас есть метод, который отвечает за достижение одной конкретной цели, но для этого ему требуется поддержка значительного числа локально ограниченных объектов, многие из которых реализуют IDisposable
,
Стандарты кодирования MS говорят, что при использовании локальных IDisposable
объекты, которые не должны "переживать" область действия метода (не будут возвращены или не будут назначены информации о состоянии некоторых более долгоживущих object
например) вы должны использовать using
построить.
Проблема в том, что в некоторых ситуациях вы можете получить вложенный "ад" using
блоки:
using (var disposableA = new DisposableObjectA())
{
using (var disposableB = new DisposableObjectB())
{
using (var disposableC = new DisposableObjectC())
{
//And so on, you get the idea.
}
}
}
Вы можете как-то смягчить это, если некоторые из используемых вами объектов происходят из общей базы или реализуют общую interface
который реализует IDisposable
, Это, конечно, связано с необходимостью сотворения указанных объектов всякий раз, когда вам нужен истинный тип объекта. Иногда это может быть жизнеспособным, пока количество кастинга не выходит из-под контроля:
using (var disposableA = new DisposableObjectA())
{
using (DisposableBaseObject disposableB = new DisposableObjectB(),
disposableC = new DisposableObjectC)
{
using (var disposableD = new DisposableObjectD())
{
//And so on, you get the idea.
}
}
}
Другой вариант не использовать using
блокировать и реализовывать напрямую try-catch
блоки. Это будет выглядеть так:
DisposableObjectA disposableA = null;
DisposableObjectB disposableB = null;
DisposableObjectC disposableC = null;
...
try
{
disposableA = new DisposableObjectA();
....
}
finally
{
if (disposableA != null)
{
disposableA.Dispose();
}
if (disposableB != null)
{
disposableB.Dispose();
}
//and so on
}
Забавно, что VS Code Analyzer помечает этот код как "неправильный". Он проинформирует вас о том, что не все возможные пути выполнения гарантируют, что все одноразовые объекты будут удалены перед выходом из области видимости. Я могу видеть это только в том случае, если какой-то объект выбрасывает во время утилизации, что, по моему мнению, никогда не должно произойти, и если это происходит, то обычно это признак того, что что-то действительно испортилось, и вам, вероятно, лучше выйти так быстро и грациозно, как вы можно из всего вашего приложения.
Итак, вопрос: какой подход тебе нравится больше? Всегда ли лучше использовать вложенные using
блоки независимо от того, сколько или после определенного предела, лучше использовать try-catch
блок?
3 ответа
Вам не нужны фигурные скобки, если есть только одно утверждение, например:
using (var disposableA = new DisposableObjectA())
using (var disposableB = new DisposableObjectB())
using (var disposableC = new DisposableObjectC())
{
//And so on, you get the idea.
}
Это зависит от того, что во внешних блоках больше ничего не происходит.
Я думаю, что вы забыли, что using
оператор (как и многие другие) не обязательно требует блока кода, но может быть одним оператором. Ваш первый пример можно записать так:
using (var disposableA = new DisposableObjectA())
using (var disposableB = new DisposableObjectB())
using (var disposableC = new DisposableObjectC())
{
//And so on, you get the idea.
}
Я думаю, что это значительно облегчает проблему. Обратите внимание, это не поможет, если вам нужно что-то делать между вызовами экземпляров, которые реализуют IDisposable
,
Я даже зашел так далеко, что вложил другие блоки, которые имеют смысл. foreach
это пример.
IEnumerable<int> ints = ...;
using (var disposableA = new DisposableObjectA())
using (var disposableB = new DisposableObjectB())
using (var disposableC = new DisposableObjectC())
foreach (int i in ints)
{
// Work with disposableA, disposableB, disposableC, and i.
}
Следует отметить, что анализатор кода VS корректен, когда сообщает, что это неверно:
DisposableObjectA disposableA = null;
DisposableObjectB disposableB = null;
DisposableObjectC disposableC = null;
...
try
{
disposableA = new DisposableObjectA();
....
}
finally
{
if (disposableA != null)
{
disposableA.Dispose();
}
if (disposableB != null)
{
disposableB.Dispose();
}
//and so on
}
Когда вы используете using
сложены друг на друга, он вкладывает их в несколько try
/finally
блоки, вот так:
DisposableObjectA disposableA = null;
DisposableObjectB disposableB = null;
DisposableObjectC disposableC = null;
...
try
{
disposableA = new DisposableObjectA();
try
{
disposableB = new DisposableObjectB();
// Try/catch block with disposableC goes here.
}
finally
{
if (disposableB != null)
{
disposableB.Dispose();
}
}
}
finally
{
if (disposableA != null)
{
disposableA.Dispose();
}
}
В вашем примере, если есть исключение, которое выдается при disposableA.Dispose
выполняется, затем disposableB
а также disposableC
не выбрасывайте finally
блок снят), если выдается ошибка, когда disposableB
называется, тогда disposableC
не закрыто и т. д.
Единственная реальная "проблема" с вашим первым образцом кода - глубокое вложение, которое может затруднить чтение и сопровождение кода. В качестве альтернативы другим ответам, предлагающим просто отбросить фигурные скобки, вы также можете обойти это путем рефакторинга наиболее глубоко вложенного кода в отдельную функцию. Это отделяет задачи создания и утилизации ваших одноразовых предметов от фактического их использования.
using (var a = new DisposableObjectA())
{
using (var b = new DisposableObjectB())
{
using (var c = new DisposableObjectC())
{
SomeFunction(a,b,c);
}
}
}