不应使用using语句和IDisposable的情况

时间:2010-07-20 17:40:30

标签: c# .net using-statement

我正在阅读this scenario,其中使用C#using语句可能会导致问题。如果在using语句末尾调用的Dispose函数也抛出异常,则可能会丢失在using块范围内抛出的异常。这突出了在决定是否添加using语句时应该注意某些情况。

在使用流和从DbConnection派生的类时,我只倾向于使用using语句。如果我需要清理非托管资源,我通常更喜欢使用finally块。

这是IDisposable接口的另一个用途,用于创建一个性能计时器,它将停止计时器并将时间记录到Dispose函数中的注册表中。 http://thebuildingcoder.typepad.com/blog/2010/03/performance-profiling.html

这是否可以很好地使用IDisposable接口?它不会清理资源或处理任何其他对象。但是,我可以看到它如何通过将正在分析的代码包装在using语句中来清理调用代码。

是否应该永远不使用using语句和IDisposable接口?在using语句中实现IDisposable或包装代码之前是否给您带来了问题?

由于

5 个答案:

答案 0 :(得分:5)

我会说,除非文档告诉你不要(如你的例子中所示),否则总是使用using

使用Dispose方法抛出异常而忽略了使用它(双关语意图)。每当我实现它时,无论对象处于什么状态,我总是尽量确保不会抛出任何异常。

PS:这是一个简单的实用方法来补偿WCF的行为。这确保了在调用Abort之外的每个执行路径中调用Close,并且错误被传达给调用者。

public static void CallSafely<T>(ChannelFactory<T> factory, Action<T> action) where T : class {
    var client = (IClientChannel) factory.CreateChannel();
    bool success = false;
    try {
        action((T) client);
        client.Close();
        success = true;
    } finally {
        if(!success) {
            client.Abort();
        }
    }
}

如果你在框架的其他地方发现任何其他有趣的行为案例,你可以提出类似的策略来处理它们。

答案 1 :(得分:3)

一般的经验法则很简单:当一个类实现IDisposable时,使用using。当您需要捕获错误时,请使用try / catch / finally,以便能够捕获错误。

然而,有一些意见。

  1. 您询问是否存在不应使用IDisposable的情况。嗯:在大多数情况下,你不需要实现它。当你想要及时释放资源时使用它,而不是等到终结者开始工作。

  2. 当实现IDisposable时,它应该意味着相应的Dispose方法清除其自己的资源并循环遍历任何引用或拥有的对象并调用它们上的Dispose。它还应标记是否已调用Dispose,以防止多次清理或引用的对象执行相同操作,从而导致无限循环。但是,所有这些并不能保证所有对当前对象的引用都消失了,这意味着它将保留在内存中,直到所有引用都消失并且终结器启动。

  3. 在Dispose中抛出异常是不受欢迎的,当它发生时,状态可能不再保证。一个令人讨厌的情况。您可以使用try / catch / finally修复它,并在finally块中添加另一个try / catch。但就像我说的那样:这很快变得丑陋。

  4. 使用using是一回事,但不要将其与使用try / finally混淆。两者都是相同的,但using语句通过添加作用域和空检查使生活变得更容易,这每次都是手工操作的痛苦。 using语句转换为此(来自C#标准):

    {
        SomeType withDispose = new SomeType();
        try
        {
             // use withDispose
        }            
        finally 
        {
            if (withDispose != null)
            {
                 ((IDisposable)withDispose).Dispose();
            }
        }
    }
    
  5. 有时无需将对象包装到使用块中。这些场合很少见。当你发现自己从继承自IDisposable 的接口继承时,就会发生这种情况,以防万一一个孩子需要处理。一个经常使用的例子是IComponent,它与每个Control一起使用(Form,EditBox,UserControl,你可以命名)。我很少看到人们在using语句中包含所有这些控件。另一个着名的例子是IEnumerator<T>。当使用它的后代时,很少会看到使用块。

  6. 结论

    无处不在地使用using语句,并明智地考虑替代方案或将其排除在外。确保你知道(不)使用它的含义,并了解using和try / finally的相等性。需要抓到什么?使用try / catch / finally。

答案 2 :(得分:2)

我认为更大的问题是在Dispose中抛出异常。 RAII模式通常明确指出不应该这样做,因为它可以创建就像这样的情况。我的意思是,除了简单地结束执行之外,没有正确处理的东西的恢复路径是什么?

此外,使用两个try-catch语句似乎可以避免这种情况:

try
{
    using(...)
    {
        try
        {
            // Do stuff
        }
        catch(NonDisposeException e)
        {
        }
    }
}
catch(DisposeException e)
{
}

此处可能出现的唯一问题是DisposeException是否相同或是NonDisposeException的超类型,并且您试图重新抛出NonDisposeException捕获。在这种情况下,DisposeException块将捕获它。所以你可能需要一些额外的布尔标记来检查这个。

答案 3 :(得分:2)

我所知道的唯一案例是WCF客户端。这是由于WCF中的设计错误 - Dispose应该从不抛出异常。他们错过了那个。

答案 4 :(得分:1)

一个例子是IAsyncResult.AsyncWaitHandle属性。精明的程序员会认识到WaitHandle类实现IDisposable并自然而然地试图贪婪地处理它们。除了BCL中APM的大多数实现实际上对属性内的WaitHandle进行延迟初始化。显然,结果是程序员完成了比必要工作更多的工作。

故障在哪里?好吧,微软搞砸了IAsyncResult界面。如果他们遵循他们自己的建议IAsyncResult将来自IDisposable,因为暗示它拥有一次性资源。精明的程序员然后只需致电Dispose IAsyncResult,让它决定如何最好地处置其成员。

这是处理IDisposable可能有问题的经典边缘案例之一。杰弗里里希特实际上使用这个例子来争辩(在我看来是错误的)调用Dispose不是强制性的。你可以阅读辩论here