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

nzkunb0c  于 2023-02-17  发布在  .NET
关注(0)|答案(5)|浏览(136)

我阅读到了关于this scenario的文章,其中使用C# using语句可能会导致问题。如果在using语句末尾调用的Dispose函数也要抛出异常,那么在using块范围内抛出的异常可能会丢失。这强调了在某些情况下,在决定是否添加using语句时应该小心。
我倾向于在使用流和从DbConnection派生的类时使用using语句。如果我需要清理非托管资源,我通常更喜欢使用finally块。
这是IDisposable接口的另一种用法,用于创建性能计时器,该计时器将停止计时器并将时间记录到Dispose函数中的注册表中。http://thebuildingcoder.typepad.com/blog/2010/03/performance-profiling.html
这是IDisposable接口的好用法吗?它没有清理资源或处理任何其他对象。但是,我可以看到它如何通过将正在分析的代码整齐地 Package 在using语句中来清理调用代码。
是否有不应该使用using语句和IDisposable接口的时候?以前在using语句中实现IDisposable或 Package 代码是否给您带来过问题?
谢谢

wkyowqbh

wkyowqbh1#

我要说的是,除非文档告诉您不要使用using(如您的示例中所示),否则请始终使用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();
        }
    }
}

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

mqxuamgl

mqxuamgl2#

一般的经验法则很简单:当类实现IDisposable时,使用using。当需要捕获错误时,使用try/catch/finally,以便能够捕获错误。
不过,有几点意见。
1.您问是否存在不应使用IDisposable的情况。在大多数情况下,你不需要实现它。当你想及时释放资源时,使用它,而不是等到终结器启动。
1.当IDisposable被实现时,它应该意味着相应的Dispose方法清除它自己的资源,并循环通过任何被引用或拥有的对象,并在它们上调用Dispose。它还应该标记Dispose是否已经被调用,以防止多次清除或被引用对象做同样的事情,从而导致无限循环。然而,所有这些并不能保证对当前对象的所有引用都消失了。这意味着它将保留在内存中,直到所有引用都消失并且终结器启动。
1.在Dispose中抛出异常是不受欢迎的,当它发生时,状态可能不再得到保证。这是一个糟糕的情况。你可以通过使用try/catch/finally来修复它,并在finally块中添加另一个try/catch。但是就像我说的:场面很快就变难看了。
1.使用using是一回事,但不要把它与使用try/finally混淆。两者是相等的,但using-statement通过添加作用域和空值检查(每次手工操作都很痛苦)使工作变得更容易。using-statement翻译如下(来自C#标准):

{
    SomeType withDispose = new SomeType();
    try
    {
         // use withDispose
    }            
    finally 
    {
        if (withDispose != null)
        {
             ((IDisposable)withDispose).Dispose();
        }
    }
}

1.有时候不需要将对象 Package 到using-block中。这种情况很少见。当您发现自己继承了从IDisposable继承的接口时,就会发生这种情况,* 以防 * 子级需要释放。一个常用的示例是IComponent,它与每个Control一起使用(Form、EditBox、UserControl,你能想到的都有)。我很少看到人们用using语句 Package 所有这些控件。另一个著名的例子是IEnumerator<T>。当使用它的后代时,也很少看到using块。

结论

普遍使用using-statement,并且要谨慎选择替代方法或忽略它。确保你知道使用(不使用)它的含义,并且知道using和try/finally的相等性。需要捕获任何东西吗?使用try/catch/finally。

svmlkihl

svmlkihl3#

我认为更大的问题是在Dispose中抛出异常。RAII模式通常明确声明不应该这样做,因为它可以创建像这样的情况。我的意思是,对于没有正确处理的东西,除了简单地结束执行之外,还有什么恢复路径呢?
而且,这似乎可以通过两个try-catch语句来避免:

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

这里唯一可能出现的问题是,如果DisposeExceptionNonDisposeException的相同类型或超类型,并且您试图重新抛出NonDisposeException捕获,则DisposeException块将捕获它,因此您可能需要一些额外的布尔标记来检查这一点。

kkbh8khc

kkbh8khc4#

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

w8biq8rn

w8biq8rn5#

一个例子是IAsyncResult.AsyncWaitHandle属性。精明的程序员会认识到WaitHandle类实现了IDisposable,并自然地试图贪婪地处理它们。除了BCL中的大多数APM实现实际上在属性内部对WaitHandle进行了惰性初始化。显然,结果是程序员做了比必要的更多的工作。
那么问题出在哪里呢?好吧,微软搞砸了IAsyncResult接口。如果他们遵循自己的建议,IAsyncResult将从IDisposable派生而来,因为其含义是它拥有可处置的资源。精明的程序员将在IAsyncResult上调用Dispose,让它决定如何最好地处置其组成部分。
这是处理IDisposable可能会有问题的典型边缘案例之一。Jeffrey Richter实际上使用这个例子来论证(在我看来是错误的)调用Dispose不是强制性的。您可以阅读here的争论。

相关问题