Избавление от вложенных выражений (...) - PullRequest
25 голосов
/ 16 мая 2010

Иногда мне нужно использовать несколько одноразовых предметов внутри функции.Наиболее распространенный случай - использование 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, чтобы класс был более гибким.Конечно, неправильное использование этой функции может привести к неправильной утилизации предметов, но есть много других способов выстрелить себе в ногу без этого класса.

Ответы [ 6 ]

47 голосов
/ 16 мая 2010

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

using (var a = new A())
using (var b = new B())
{
    /// ...
}
20 голосов
/ 16 мая 2010

Несколько замечаний об общем принципе:

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

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

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

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

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

13 голосов
/ 16 мая 2010

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

 using (StreamReader rdr = new StreamReader(args[0])) 
 using (StreamWriter wrt = new StreamWriter(args[1])) 
 {     
   // code 
 }
7 голосов
/ 16 мая 2010

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

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

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

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

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

4 голосов
/ 16 мая 2010

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

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

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

   // do stuff

}
2 голосов
/ 17 мая 2010

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

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

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

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

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

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

...