【发布时间】:2013-10-13 14:34:11
【问题描述】:
我注意到最近在我的代码中嵌套的using 语句的级别有所增加。原因可能是因为我使用的async/await 模式越来越多,这通常会为CancellationTokenSource 或CancellationTokenRegistration 添加至少一个using。
那么,如何减少using的嵌套,让代码看起来不像圣诞树呢?之前在 SO 上也有人问过类似的问题,我想总结一下我从答案中学到的东西。
使用相邻的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);
// ...
}
[更新] 在 cmets 中有很多有效点,即嵌套 using 语句可确保在每个对象上调用 Dispose,即使某些内部 Dispose 调用抛出.然而,有一个有点模糊的问题:除了最外层的之外,所有可能由处理嵌套的“使用”帧引发的嵌套异常都将丢失。更多关于这个here。
【问题讨论】:
-
您可以尝试方法提取等技术。我的意思是尝试将这个特定的方法分成小的独立部分并将它们移动到方法中。这样您就可以将这多个
using块移动到不同的方法中。 -
通常,如果您使用多个,比如说,2 个嵌套的
using语句,那么您的方法无论如何都有点过于复杂,因此无论如何都需要重构。如果您或多或少地遵循“干净代码”原则,您通常不会最终嵌套太多using语句。 @MuctadirDinar:同样的想法! -
我通常发现 3 是我将嵌套的最多的,而且我发现正常的嵌套缩进非常可读,并且比您提出的任何其他替代方案都更清晰。也许在 4 或 5 之后它可能会变得有点棘手,但即便如此,当我阅读代码时,我宁愿有明显的代码,而不是非标准模式来研究。现在的显示器一般都很宽,所以我不会太担心水平空间。
-
是的。考虑在极端情况下进行重构。无论如何,代码通常都需要 cmets,而重构(拆分为方法)是注释代码并使其更具可读性的理想方式。
-
3 显然很糟糕:它需要特别努力才能在编写代码时不要忘记处理对象,并产生难以阅读的异常代码。旁注:如问题 3 所示,如果早先的
Dispose抛出异常,则变体可能永远不会处理某些对象。
标签: c#