是否有一种 using/Dispose 语法更容易处理但与链接一样安全?

Is there a using/Dispose syntax that will be more eager to dispose but just as safe as chaining?

如果我正在执行以下操作:

using (var foo = bar.GetFoo())
using (var blat = new Blat(foo))
using (var bonk = Bonk.FromBlat(blat))
using (var bork = Bob.FromBip(bonk))
{
    var target = bork.ToArray(); // I GOT THE TARGET
}

它非常安全且可读,但我的理解是,只有在最后一行之后,整个事情才会展开以处理所有内容。鉴于fooblatbonk只用单行来创建bork,有没有好的方法("good"当然是主观的)尽快处理它们以释放资源?我真的很喜欢 using 链的安全性和可读性,我只是好奇是否有另一种方法可以做到这一点。

版本 1 和版本 2 在功能上并不相同,因为在您的伪代码 2 中处理顺序应该颠倒:

Foo foo = null;
Blat blat = null;
Bonk bonk = null;
Bork bork = null;
try
{
    foo = bar.GetFoo();
    blat = new Blat(foo);
    bonk = Bonk.FromBlat(blat);
    bork = Bob.FromBip(bonk);
    var target = bork.Whatever; // I GOT THE TARGET
}
finally
{   // the disposal should happen in reverse order to instantiation
    bork?.Dispose();
    bonk?.Dispose();
    blat?.Dispose();
    foo?.Dispose();
}

看到您是如何弄错顺序的,您可能会理解为什么使用链式 using 语句可以确保事情以正确的顺序发生,而不是依赖您来获得正确的结果

如果编译器可以确保事情按正确的顺序完成 - 让编译器为你做 - 更少的错误机会

您可以编写一个方法来使用生成值的 IDisposable。这里的部分问题是 using 语句作为语句不会产生值,因此很难链接:

public static TResult Use<TSource, TResult>(this TSource source, Func<TSource, TResult> selector)
    where TSource : IDisposable
{
    using (source)
        return selector(source);
}

这让你写:

var target = bar.GetFoo()
    .Use(foo => new Blat(foo))
    .Use(blat => Bonk.FromBlat(blat))
    .Use(bonk => Bob.FromBonk(bonk))
    .Use(bork => bork.ToArray());

请注意,此 Use 方法从外部接受 IDisposable,但会自行处理它,因此如果您在 IDisposable 上调用 Use如果你仍然在范围内,即使在它被处理后你仍然可以使用它,should 错误。在这种情况下,我们永远不会抓住一次性物品,但其他人可能不知道他们不应该这样做。

如果您想解决这个问题,您可以编写一个将一次性值链接在一起的函数。通过把它全部放在一个函数中,你可以确保没有一次性 "leak" 很容易地从它出来:

public static TResult ChainDisposables<T1, T2, T3, T4, TResult>(Func<T1> firstFunc,
    Func<T1, T2> secondFunc,
    Func<T2, T3> thirdFunc,
    Func<T3, T4> fourthFunc,
    Func<T4, TResult> resultFunc)
    where T1 : IDisposable
    where T2 : IDisposable
    where T3 : IDisposable
    where T4 : IDisposable
{
    return firstFunc()
        .Use(secondFunc)
        .Use(thirdFunc)
        .Use(fourthFunc)
        .Use(resultFunc);
}

(您需要按照示例为链中每个不同数量的对象创建重载。)

哪个会让你写:

var target = ChainDisposables(() => bar.GetFoo(),
    foo => new Blat(foo),
    blat => Bonk.FromBlat(blat),
    bonk => Bob.FromBonk(bonk),
    bork => bork.ToArray());

3 年后我可以报告说,2019 年 9 月发布的 C# 8 添加了内联 using 语句。我的问题是“我很好奇是否还有另一种方法可以做到这一点。”现在还有另一种方法。

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement

我不能说处理是否更急切地发生了,但对我来说它看起来更好,括号和圆括号更少,而且在某些情况下还避免了嵌套。