处理C#中嵌套的"using"语句

nos*_*tio 30 c#

我注意到using我的代码中嵌套语句的级别最近有所增加.究其原因,可能是因为我使用越来越多的async/await格局,这往往增加了至少一个以上usingCancellationTokenSourceCancellationTokenRegistration.

那么,如何减少嵌套using,所以代码看起来不像圣诞树?之前已经提出了类似的问题,我想总结一下我从答案中学到的东西.

使用邻近using而不缩进.一个假的例子:

using (var a = new FileStream())
using (var b = new MemoryStream())
using (var c = new CancellationTokenSource())
{
    // ... 
}
Run Code Online (Sandbox Code Playgroud)

这可能有用,但通常会有一些代码using(例如,创建另一个对象可能为时过早):

// ... 
using (var a = new FileStream())
{
    // ... 
    using (var b = new MemoryStream())
    {
        // ... 
        using (var c = new CancellationTokenSource())
        {
            // ... 
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

将相同类型(或强制转换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()))
{
    // ... 
}
Run Code Online (Sandbox Code Playgroud)

这与上面有相同的限制,加上更加冗长和不易读,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);
    // ... 
}
Run Code Online (Sandbox Code Playgroud)

[更新]注释中有很多有效点,嵌套using语句确保Dispose将在每个对象上调用,即使某些内部Dispose调用抛出.但是,有一个模糊的问题:除了最外层的框架之外,所有嵌套的"使用"框架可能抛出的嵌套异常都将丢失.更多关于这里.

Mik*_*ray 16

在单一方法中,第一个选项将是我的选择.但是在某些情况下这DisposableList是有用的.特别是,如果您有许多需要处理的一次性场地(在这种情况下您不能使用using).给出的实现是一个良好的开端,但它有一些问题(在Alexei的评论中指出):

  1. 要求您记住将项目添加到列表中.(虽然你也可以说你必须记得使用using.)
  2. 如果其中一种处置方法抛出,则中止处理过程,而剩余的物品不予处理.

让我们解决这些问题:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们从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());
    // ... 
}
Run Code Online (Sandbox Code Playgroud)


use*_*389 6

你应该总是参考你的假例子.如果不可能,就像你提到的那样,那么很有可能你可以将内部内容重构为一个单独的方法.如果这也没有意义,你应该坚持你的第二个例子.其他所有东西看起来都不那么可读,不太明显,也不太常见.


bol*_*oli 6

我会坚持使用块.为什么?

  • 它清楚地显示了您对这些对象的意图
  • 你不必乱用try-finally块.它容易出错,而且代码的可读性也会降低.
  • 您可以稍后重构嵌入式语句(将它们提取到方法中)
  • 你不要通过创建自己的逻辑来混淆你的程序员,包括新的抽象层