我注意到嵌套using
语句的级别最近在我的代码中增加了。原因可能是因为我使用了越来越多的async/await
模式,这通常会为using
或CancellationTokenSource
增加至少一个CancellationTokenRegistration
。
那么,如何减少using
的嵌套,所以代码看起来不像圣诞树?之前已经问过类似的问题,我想总结一下我从答案中学到的东西。
using
而不缩进。一个假的例子:using (var a = new FileStream())
using (var b = new MemoryStream())
using (var c = new CancellationTokenSource())
{
// ...
}
这可能有用,但通常会在using
之间有一些代码(例如,创建另一个对象可能为时尚早):
// ...
using (var a = new FileStream())
{
// ...
using (var b = new MemoryStream())
{
// ...
using (var c = new CancellationTokenSource())
{
// ...
}
}
}
IDisposable
)合并为单个using
,例如:// ...
FileStream a = null;
MemoryStream b = null;
CancellationTokenSource c = null;
// ...
using (IDisposable a1 = (a = new FileStream()),
b1 = (b = new MemoryStream()),
c1 = (c = new CancellationTokenSource()))
{
// ...
}
这与上面有相同的限制,加上更加冗长和不易阅读,IMO。
据我了解,这是首选方式。 然而,我很好奇,为什么以下被视为不良做法?
public class DisposableList : List<IDisposable>, IDisposable
{
public void Dispose()
{
base.ForEach((a) => a.Dispose());
base.Clear();
}
}
// ...
using (var disposables = new DisposableList())
{
var a = new FileStream();
disposables.Add(a);
// ...
var b = new MemoryStream();
disposables.Add(b);
// ...
var c = new CancellationTokenSource();
disposables.Add(c);
// ...
}
[更新] 评论中有相当多的有效点,嵌套using
语句可确保在每个对象上调用Dispose
,即使某些内部{ {1}}调用throw。但是,有一个模糊的问题:除了最外层的框架之外,所有嵌套的“使用”框架可能抛出的嵌套异常都将丢失。有关here的更多信息。
答案 0 :(得分:16)
在单一方法中,第一个选项将是我的选择。但是在某些情况下DisposableList
很有用。特别是,如果您有许多需要处理的一次性字段(在这种情况下您不能使用using
)。给出的实现是一个良好的开端,但它有一些问题(阿列克谢的评论中指出):
using
。)让我们解决这些问题:
public class DisposableList : List<IDisposable>, IDisposable
{
public void Dispose()
{
if (this.Count > 0)
{
List<Exception> exceptions = new List<Exception>();
foreach(var disposable in this)
{
try
{
disposable.Dispose();
}
catch (Exception e)
{
exceptions.Add(e);
}
}
base.Clear();
if (exceptions.Count > 0)
throw new AggregateException(exceptions);
}
}
public T Add<T>(Func<T> factory) where T : IDisposable
{
var item = factory();
base.Add(item);
return item;
}
}
现在我们从Dispose
调用中捕获任何异常,并在遍历所有项目后抛出新的AggregateException
。我添加了一个帮助Add
方法,允许更简单的使用:
using (var disposables = new DisposableList())
{
var file = disposables.Add(() => File.Create("test"));
// ...
var memory = disposables.Add(() => new MemoryStream());
// ...
var cts = disposables.Add(() => new CancellationTokenSource());
// ...
}
答案 1 :(得分:6)
你应该总是参考你的假例子。如果不可能,就像你提到的那样,那么很可能你可以将内部内容重构为一个单独的方法。如果这也没有意义,你应该坚持你的第二个例子。其他所有内容似乎都不那么易读,不那么明显,也不太常见。
答案 2 :(得分:6)
我会坚持使用块。为什么呢?
答案 3 :(得分:1)
您的上一个建议隐藏了a
,b
和c
应明确处理的事实。这就是为什么它很难看。
正如我的评论中提到的,如果你使用干净的代码原则,你就不会遇到这些问题(通常)。
答案 4 :(得分:1)
另一种选择是简单地使用try-finally
块。这可能看起来有点冗长,但它确实减少了不必要的嵌套。
FileStream a = null;
MemoryStream b = null;
CancellationTokenSource c = null;
try
{
a = new FileStream();
// ...
b = new MemoryStream();
// ...
c = new CancellationTokenSource();
}
finally
{
if (a != null) a.Dispose();
if (b != null) b.Dispose();
if (c != null) c.Dispose();
}