使用大量块
本文关键字: | 更新日期: 2023-09-27 17:58:24
我想听听你对以下主题的看法:
假设我们有一个方法负责实现一个特定的目的,但要做到这一点,它需要大量本地范围对象的支持,其中许多对象实现IDisposable
。
MS编码标准规定,当使用不需要在方法的范围内"存活"的本地IDisposable
对象(例如,不会返回或不会分配给一些寿命更长的object
的状态信息)时,应该使用using
构造。
问题是,在某些情况下,你可以得到一个嵌套的using
块的"地狱":
using (var disposableA = new DisposableObjectA())
{
using (var disposableB = new DisposableObjectB())
{
using (var disposableC = new DisposableObjectC())
{
//And so on, you get the idea.
}
}
}
如果您正在使用的某些对象派生自公共基或实现实现IDisposable
的公共interface
,则可以以某种方式减轻这种情况。当然,这是以必须在需要对象的真实类型时投射所述对象为代价的。有时,只要铸造量不失控,这是可行的:
using (var disposableA = new DisposableObjectA())
{
using (DisposableBaseObject disposableB = new DisposableObjectB(),
disposableC = new DisposableObjectC)
{
using (var disposableD = new DisposableObjectD())
{
//And so on, you get the idea.
}
}
}
另一种选择是不使用using
块,而是直接实现try-catch
块。这看起来像:
DisposableObjectA disposableA = null;
DisposableObjectB disposableB = null;
DisposableObjectC disposableC = null;
...
try
{
disposableA = new DisposableObjectA();
....
}
finally
{
if (disposableA != null)
{
disposableA.Dispose();
}
if (disposableB != null)
{
disposableB.Dispose();
}
//and so on
}
有趣的是,VS代码分析器会将此代码标记为"错误"。它将通知您,并不是所有可能的执行路径都能确保所有可丢弃对象在超出范围之前得到处理。我只能看到,如果在处理过程中有物体抛出,我认为这是永远不应该发生的,如果确实发生了,这通常是一个迹象,表明发生了一些真正糟糕的事情,你可能最好从整个应用程序中尽快优雅地退出。
所以,问题是:你更喜欢什么方法?无论有多少块,使用嵌套的using
块总是更好的吗?或者,超过一定的限制,使用try-catch
块更好吗?
如果只有一条语句,则不需要花括号,例如:
using (var disposableA = new DisposableObjectA())
using (var disposableB = new DisposableObjectB())
using (var disposableC = new DisposableObjectC())
{
//And so on, you get the idea.
}
不过,这确实取决于外部区块中没有发生任何其他事情。
我想您已经忘记了using
语句(和许多其他语句一样)不一定需要代码块,而是可以是单个语句。你的第一个例子可以写成:
using (var disposableA = new DisposableObjectA())
using (var disposableB = new DisposableObjectB())
using (var disposableC = new DisposableObjectC())
{
//And so on, you get the idea.
}
我认为这极大地缓解了问题。请注意,如果您需要在调用实现IDisposable
的实例之间做一些事情,这并没有帮助。
我甚至会去嵌套其他有意义的块。CCD_ 14就是一个例子。
IEnumerable<int> ints = ...;
using (var disposableA = new DisposableObjectA())
using (var disposableB = new DisposableObjectB())
using (var disposableC = new DisposableObjectC())
foreach (int i in ints)
{
// Work with disposableA, disposableB, disposableC, and i.
}
需要注意的是,当VS代码分析器告诉您这是不正确的时,它是正确的:
DisposableObjectA disposableA = null;
DisposableObjectB disposableB = null;
DisposableObjectC disposableC = null;
...
try
{
disposableA = new DisposableObjectA();
....
}
finally
{
if (disposableA != null)
{
disposableA.Dispose();
}
if (disposableB != null)
{
disposableB.Dispose();
}
//and so on
}
当您使用堆叠在一起的using
时,它会将它们嵌套在多个try
/finally
块中,如下所示:
DisposableObjectA disposableA = null;
DisposableObjectB disposableB = null;
DisposableObjectC disposableC = null;
...
try
{
disposableA = new DisposableObjectA();
try
{
disposableB = new DisposableObjectB();
// Try/catch block with disposableC goes here.
}
finally
{
if (disposableB != null)
{
disposableB.Dispose();
}
}
}
finally
{
if (disposableA != null)
{
disposableA.Dispose();
}
}
在您的示例中,如果在执行disposableA.Dispose
时抛出异常,则不会处理disposableB
和disposableC
(finally
块已退出),如果在调用disposableB
时抛出错误,则disposableC
不会关闭,等等。
第一个代码示例唯一真正的"问题"是深度嵌套,这会使读取和维护代码变得困难。作为其他建议您只需去掉大括号的答案的替代方案,您还可以通过将嵌套最深的代码重构为一个单独的函数来解决这一问题。这就把创建和处理一次性物品与实际使用它们的问题分开了。
using (var a = new DisposableObjectA())
{
using (var b = new DisposableObjectB())
{
using (var c = new DisposableObjectC())
{
SomeFunction(a,b,c);
}
}
}