8

我正在阅读这种情况,其中使用 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 或包装代码是否给您带来了问题?

谢谢

4

5 回答 5

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();
        }
    }
}

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

于 2010-07-20T17:46:06.750 回答
3

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

然而,有一些观察。

  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. 在某些情况下,不需要将对象包装到 using 块中。这些场合很少见。当您发现自己从继承自 IDisposable 的接口继承时,它们就会发生,以防孩子需要处置。一个经常使用的示例是 IComponent,它与每个控件(Form、EditBox、UserControl 等)一起使用。而且我很少看到有人将所有这些控件包装在 using 语句中。另一个著名的例子是IEnumerator<T>. 在使用它的后代时,也很少看到 using-blocks。

结论

无处不在地使用 using 语句,并谨慎选择替代方案或将其排除在外。using确保您知道(不)使用它的含义,并注意和 try/finally的相等性。需要抓什么东西吗?使用 try/catch/finally。

于 2010-07-20T18:11:24.497 回答
2

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

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

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

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

于 2010-07-20T17:49:34.733 回答
2

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

于 2010-07-20T18:24:15.773 回答
1

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

那么故障在哪里呢?好吧,微软搞砸了IAsyncResult界面。如果他们遵循自己的建议IAsyncResult,就会得出IDisposable这样的结论,因为这意味着它拥有可支配的资源。然后,精明的程序员会调用DisposeIAsyncResult让它决定如何最好地处理它的组成部分。

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

于 2010-07-20T17:59:53.380 回答