Подтвердить что ты не робот

Избавление от вложенных операторов (...)

Иногда мне нужно использовать несколько одноразовых объектов внутри функции. Наиболее распространенным случаем является StreamReader и StreamWriter, но иногда это даже больше, чем это.

Вложенные операторы быстро складываются и выглядят уродливыми. Чтобы исправить это, я создал небольшой класс, который собирает объекты IDisposable и удаляет их, когда он сам расположен.

public class MultiDispose : HashSet<IDisposable>, IDisposable
{
    public MultiDispose(params IDisposable[] objectsToDispose)
    {
        foreach (IDisposable d in objectsToDispose)
        {
            this.Add(d);
        }
    }

    public T Add<T>(T obj) where T : IDisposable
    {
        base.Add(obj);
        return obj;
    }

    public void DisposeObject(IDisposable obj)
    {
        obj.Dispose();
        base.Remove(obj);
    }


    #region IDisposable Members

    public void Dispose()
    {
        foreach (IDisposable d in this)
        {
            d.Dispose();
        }

    }

    #endregion
}

Итак, теперь мой код выглядит так:

        using (MultiDispose md = new MultiDispose())
        {
            StreamReader rdr = md.Add(new StreamReader(args[0]));
            StreamWriter wrt = md.Add(new StreamWriter(args[1]));
            WhateverElseNeedsDisposing w = md.Add(new WhateverElseNeedsDisposing());

            // code
        }

Есть ли что-то не так с этим подходом, которые могут вызвать проблемы в будущем? Я оставил функцию Remove, унаследованную от HashSet, для того, чтобы класс был более гибким. Разумеется, злоупотребление этой функцией может привести к тому, что объекты не будут утилизированы должным образом, но тогда есть много других способов стрелять в ногу без этого класса.

4b9b3361

Ответ 1

Вы могли бы просто сделать это:

using (var a = new A())
using (var b = new B())
{
    /// ...
}

Ответ 2

Несколько пунктов об общем принципе:

  • Ваш код явно не идиоматический С#. В основном вы просите кого-нибудь, кто работает с вашим кодом, взять на борт необычный стиль для очень мало пользы.
  • Как указывали другие, вы можете вставлять выражения using без дополнительных привязок
  • Если вы обнаружите, что используете несколько операторов в одном методе, вам может потребоваться разбить его на более мелкие методы.
  • Если у вас есть две переменные того же типа, вы можете использовать один оператор using:

    using (Stream input = File.OpenRead("input.dat"),
           output = File.OpenWrite("output.dat"))
    {
    }
    

Теперь, предполагая, что вы действительно хотите продолжить это:

  • Ваш код будет распоряжаться содержащимися в нем ресурсами в жестком предсказуемом порядке. Вместо использования набора он должен вставить список - и затем утилизировать вещи в обратном порядке для вызовов Add.
  • Нет причин извлекать из HashSet<T> или даже любой коллекции. Вы должны просто иметь список внутри класса как частную переменную-член.
  • Если один из вызовов Dispose завершается с ошибкой, ни один из других вызовов Dispose не будет выполнен; с традиционным выражением using, каждый вызов Dispose выполняется в пределах своего собственного блока finally.

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

Ответ 3

Возможно, просто вы показали простой пример, но я думаю, что следующее более читаемо.

 using (StreamReader rdr = new StreamReader(args[0])) 
 using (StreamWriter wrt = new StreamWriter(args[1])) 
 {     
   // code 
 }

Ответ 4

Вы можете сделать вложенные инструкции using более красивыми, используя только одну пару фигурных скобок, например:

using (StreamReader rdr = new StreamReader(args[0])) 
using (StreamWriter wrt = new StreamWriter(args[1])) 
{
    ///...
}

Чтобы ответить на ваш вопрос, вам нужно распорядиться в противоположном порядке добавления.
Поэтому вы не можете использовать HashSet.

Кроме того, нет причин раскрывать список IDisposable для внешнего мира.
Следовательно, вы не должны наследовать какой-либо класс коллекции и вместо этого сохранять частный List<IDisposable>.

Затем вы должны иметь общедоступные методы Add<T> и Dispose (и другие методы), а затем прокручивать список назад в Dispose.

Ответ 5

Лично это заводило бы меня с ума. Если вы обнаруживаете, что вложенные операторы могут быть раздражающими, вы можете вернуться к синтаксису try/finally. Dispose методы не должны бросать исключения, поэтому вы можете предположить, что несколько одноразовых предметов не нужно будет индивидуально упаковывать в блоки try/finally.

Также стоит отметить, что вам нужен только один набор скобок для смежных блоков, например:

using (var stream = File.Open(...))
using (var reader = new StreamReader(stream)) {

   // do stuff

}

Ответ 6

Я должен сказать, что я не согласен с людьми, которые хотят делать заявления один за другим, например:

using (var a = new StreamReader())
using (var b = new StreamWriter())
{
 // Implementation
}

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

Я бы поставил это на что-то вроде:

if (someBoolean) DoSomething();
{
  // Some code block unrelated to the if statement
}

Технически это не неверно, но это ужасно смотреть.

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