我有一个 C# 类 Dispose 功能通过 IDisposable. 。它旨在用于内部 using 块,以便可以立即释放它处理的昂贵资源。

问题是之前抛出异常时出现了bug Dispose 被调用,并且程序员忽略了使用 using 或者 finally.

在 C++ 中,我从来不用担心这个。对类的析构函数的调用将自动插入到对象作用域的末尾。避免这种情况发生的唯一方法是使用 new 运算符并将对象保存在指针后面,但这对程序员来说需要额外的工作,这并不是他们偶然会做的事情,比如忘记使用 using.

有什么办法可以 using 在 C# 中自动使用块?

非常感谢。

更新:

我想解释一下为什么我不接受终结器的答案。这些答案本身在技术上是正确的,但它们不是 C++ 风格的析构函数。

这是我发现的错误,简化为要点......

try
{
    PleaseDisposeMe a = new PleaseDisposeMe();
    throw new Exception();
    a.Dispose();
}
catch (Exception ex)
{
    Log(ex);
}

// This next call will throw a time-out exception unless the GC
// runs a.Dispose in time.
PleaseDisposeMe b = new PleaseDisposeMe();

使用 FXCop 是一个很好的建议,但如果这是我唯一的答案,我的问题就必须成为对 C# 人员的恳求,或者使用 C++。有人知道二十个嵌套的 using 语句吗?

有帮助吗?

解决方案

不幸的是,没有任何方法可以直接在代码中执行此操作。如果这是内部问题,有各种代码分析解决方案可以捕获此类问题。你调查过FxCop吗?我认为这将捕获这些情况以及 IDisposable 对象可能挂起的所有情况。如果它是人们在组织外部使用的组件,并且您不需要 FxCop,那么文档实际上是您唯一的资源:)。

编辑:对于终结器来说,这并不能真正保证终结器何时发生。所以这可能是您的解决方案,但这取决于具体情况。

其他提示

在我工作的地方,我们遵循以下准则:

  • 每个 IDisposable 类 必须 有一个终结器
  • 每当使用 IDisposable 对象时,都必须在“using”块内使用它。唯一的例外是,如果该对象是另一个类的成员,在这种情况下,包含类必须是 IDisposable,并且必须在其自己的“Dispose”实现中调用该成员的“Dispose”方法。这意味着开发人员永远不应该调用“Dispose”,除非在另一个“Dispose”方法内,从而消除了问题中描述的错误。
  • 每个终结器中的代码必须以警告/错误日志开头,通知我们终结器已被调用。这样,您就有很大的机会在发布代码之前发现如上所述的错误,而且它可能是系统中发生错误的提示。

为了让我们的生活更轻松,我们的基础设施中还有一个 SafeDispose 方法,它在 try-catch 块中调用其参数的 Dispose 方法(带有错误日志记录),以防万一(尽管 Dispose 方法不应该抛出异常)。

也可以看看: 克里斯·里昂关于 IDisposable 的建议

编辑:@吵架:您应该做的一件事是在“Dispose”内调用 GC.SuppressFinalize,这样如果对象被处置,它就不会被“重新处置”。

通常还建议保留一个标志来指示该对象是否已被处置。以下模式通常非常好:

class MyDisposable: IDisposable {
    public void Dispose() {
        lock(this) {
            if (disposed) {
                return;
            }

            disposed = true;
        }

        GC.SuppressFinalize(this);

        // Do actual disposing here ...
    }

    private bool disposed = false;
}

当然,锁定并不总是必要的,但如果您不确定您的类是否会在多线程环境中使用,建议保留它。

@吵架

当对象移出范围并被垃圾收集器整理时,if 将被调用。

这种说法具有误导性,我的理解是错误的:绝对不能保证何时调用终结器。你说 billpg 应该实现终结器是完全正确的;然而,当对象超出他想要的范围时,它不会被自动调用。 证据, ,下面的第一个要点 Finalize 操作有以下限制.

事实上,微软向 Chris Sells 提供了一笔资助,以创建使用引用计数而不是垃圾收集的 .NET 实现。 关联. 。事实证明有一个 大量 性能受到打击。

~ClassName()
{
}

编辑(粗体):

当对象移出范围并被垃圾收集器整理时,if 将被调用 但这不是确定性的,并且不能保证在任何特定时间发生。这称为终结器。所有具有终结器的对象都会被垃圾收集器放入一个特殊的终结队列,并在其中调用它们的终结方法(因此从技术上讲,声明空终结器会影响性能)。

根据框架指南,对于非托管资源,“接受”的处置模式如下:

    public class DisposableFinalisableClass : IDisposable
    {
        ~DisposableFinalisableClass()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // tidy managed resources
            }

            // tidy unmanaged resources
        }
    }

所以上面的意思是,如果有人调用 Dispose,非托管资源就会被整理。然而,如果有人忘记调用 Dispose 或发生阻止调用 Dispose 的异常,非托管资源仍然会被清理掉,只是稍后 GC 开始清理(包括应用程序关闭或意外结束) )。

最佳实践是在类中使用终结器并始终使用 using 块。

虽然没有真正的直接等价物,终结器看起来像 C 析构函数,但行为不同。

你应该筑巢 using 块,这就是为什么 C# 代码布局默认将它们放在同一行......

using (SqlConnection con = new SqlConnection("DB con str") )
using (SqlCommand com = new SqlCommand( con, "sql query") )
{
    //now code is indented one level
    //technically we're nested twice
}

当你不使用时 using 无论如何,你可以做它在幕后所做的事情:

PleaseDisposeMe a;
try
{
    a = new PleaseDisposeMe();
    throw new Exception();
}
catch (Exception ex) { Log(ex); }  
finally {    
    //this always executes, even with the exception
    a.Dispose(); 
}

对于托管代码,C# 非常擅长管理自己的内存,即使在处理不当的情况下也是如此。如果您经常处理非托管资源,那么它就不那么强大了。

这与程序员忘记使用没有什么不同 删除 在 C++ 中,除了至少在这里垃圾收集器最终仍会赶上它。

如果您唯一担心的资源是内存,那么您永远不需要使用 IDisposable。该框架将自行处理该问题。IDisposable 仅适用于非托管资源,例如数据库连接、文件流、套接字等。

更好的设计是让此类在处置之前自行释放昂贵的资源。

例如,如果它是数据库连接,则仅在需要时连接并立即释放,远远早于实际的类被释放。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top