Используя блок в изобилии?

Я хотел бы, чтобы ваше мнение по следующей теме:

Представьте, что у нас есть метод, который отвечает за достижение одной конкретной цели, но для этого ему требуется поддержка значительного числа локально ограниченных объектов, многие из которых реализуют 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);
         }
    }
}
Другие вопросы по тегам