Ответ 1
Вы могли бы просто сделать это:
using (var a = new A())
using (var b = new B())
{
/// ...
}
Иногда мне нужно использовать несколько одноразовых объектов внутри функции. Наиболее распространенным случаем является 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, для того, чтобы класс был более гибким. Разумеется, злоупотребление этой функцией может привести к тому, что объекты не будут утилизированы должным образом, но тогда есть много других способов стрелять в ногу без этого класса.
Вы могли бы просто сделать это:
using (var a = new A())
using (var b = new B())
{
/// ...
}
Несколько пунктов об общем принципе:
using
без дополнительных привязокЕсли у вас есть две переменные того же типа, вы можете использовать один оператор using:
using (Stream input = File.OpenRead("input.dat"),
output = File.OpenWrite("output.dat"))
{
}
Теперь, предполагая, что вы действительно хотите продолжить это:
Add
.HashSet<T>
или даже любой коллекции. Вы должны просто иметь список внутри класса как частную переменную-член.Dispose
завершается с ошибкой, ни один из других вызовов Dispose
не будет выполнен; с традиционным выражением using
, каждый вызов Dispose
выполняется в пределах своего собственного блока finally
.В принципе, я думаю, что это плохая идея. Вложение двух уровней глубоко далеко не больно; вложенные три должны быть редкими; Вложение четырех или более настоятельно предлагает рефакторинг. Вместо того, чтобы пытаться справиться с болью глубокого гнездования, вы должны проектировать ее.
Возможно, просто вы показали простой пример, но я думаю, что следующее более читаемо.
using (StreamReader rdr = new StreamReader(args[0]))
using (StreamWriter wrt = new StreamWriter(args[1]))
{
// code
}
Вы можете сделать вложенные инструкции using
более красивыми, используя только одну пару фигурных скобок, например:
using (StreamReader rdr = new StreamReader(args[0]))
using (StreamWriter wrt = new StreamWriter(args[1]))
{
///...
}
Чтобы ответить на ваш вопрос, вам нужно распорядиться в противоположном порядке добавления.
Поэтому вы не можете использовать HashSet
.
Кроме того, нет причин раскрывать список IDisposable
для внешнего мира.
Следовательно, вы не должны наследовать какой-либо класс коллекции и вместо этого сохранять частный List<IDisposable>
.
Затем вы должны иметь общедоступные методы Add<T>
и Dispose
(и другие методы), а затем прокручивать список назад в Dispose
.
Лично это заводило бы меня с ума. Если вы обнаруживаете, что вложенные операторы могут быть раздражающими, вы можете вернуться к синтаксису try/finally. Dispose методы не должны бросать исключения, поэтому вы можете предположить, что несколько одноразовых предметов не нужно будет индивидуально упаковывать в блоки try/finally.
Также стоит отметить, что вам нужен только один набор скобок для смежных блоков, например:
using (var stream = File.Open(...))
using (var reader = new StreamReader(stream)) {
// do stuff
}
Я должен сказать, что я не согласен с людьми, которые хотят делать заявления один за другим, например:
using (var a = new StreamReader())
using (var b = new StreamWriter())
{
// Implementation
}
На мой взгляд, это очень нечитаемое - наличие какого-либо блока кода, который не завершен, просто плохой стиль, и может привести к проблемам, если все разработчики, работающие над ним, не будут очень осторожны.
Я бы поставил это на что-то вроде:
if (someBoolean) DoSomething();
{
// Some code block unrelated to the if statement
}
Технически это не неверно, но это ужасно смотреть.
Я согласен с тем, что концепция MultiDispose, вероятно, не самая лучшая идея из-за того, что это не принятый шаблон, но я определенно не пошел бы этим путем. Если вы не можете разбить вещи на более мелкие куски, я бы предложил просто жить с вложенными потребностями.