在 Dispose 方法中应该完成多少工作?
这取决于,您是为了方便“使用”语句而实现 IDispose 接口,还是实现完整的 IDisposable 模式?在完全一次性模式的后一种情况下,如果您的“处置”参数为真(即您不在 GC 中),执行更复杂的操作仍然是可以接受的。
当您定义一个调用 Dispose 方法的终结器时,确实没有太多需要担心的。已经提到的 IDisposable 接口的类似使用/滥用(即using (Html.BeginForm()))能够执行任何操作。通常这可以大大降低代码复杂性并防止编码人员意外忘记执行某些关闭操作。一个向下(或向上)的一面是code executes a little differently inside a finally block。
在构造函数中,我一直认为你应该只做实例化对象绝对必要的事情。
对象,恕我直言,应该是有效的后期构造。所以如果你有很多工作要做来构建一些东西,那就这样吧。不要考虑所涉及的工作量,想想你的消费者是对象和它的可用性。构造后 Initialize() 方法很烂;)
在这种情况下,我也一直采取的方法是,您应该只在处置时清理开放资源。关闭文件、释放内存、处置子一次性对象等。您不应该在 Dispose 方法中执行诸如触摸文件、访问数据库等冗长的过程。
其实让我们稍微分解一下...
从 GC 调用处理到 Finalizer
当您实现 IDisposable 模式(不是接口、模式、终结器等等)时,您实际上是在说您的对象有一个其他人都不知道的非托管资源。这意味着您已经 PInvoked 对 Win32 的 CreateFile 的调用,或者您可能调用了 Marshal.AllocHGlobal 或类似的东西。本质上,您可能有一个 IntPtr 实例成员,您需要做一些事情来防止内存泄漏。当 disposing 参数为 false 时(即从 GC 线程上的终结器调用),这些是唯一应该做的事情。
一般情况下,您不会对孩子调用 Dispose 方法。您不应期望任何子对象都是有效的。简单地触摸子对象的成员可能会意外“复活”或resurrect it。
因此,当您编写在从 Finalizer 调用的 Dispose 方法中执行的代码时,您必须小心。您正在 GC 线程上执行,而应用程序的其余部分正在等待您。您应该执行尽可能少的操作来释放非托管内存/资源并退出。永远不要抛出异常,如果您正在调用可能抛出的 API,您应该捕获任何引发的异常。将异常传播回 GC 会过早中止终结器线程,并且要终结的剩余对象将没有机会清理。
通过 IDisposable.Dispose() 方法进行处理
正如我已经说过的,使用 Dispose 方法足够安全,并且可以安全地容纳任意数量的代码/进程。在这里您可以释放非托管资源、调用子对象的 dispose 方法、刷新和关闭文件等。我编写的大多数 Dispose 方法没有关联的 Finalizer,因此不遵循 IDisposable 模式,但它们实现 IDisposable 只是为了方便 using 语句。
我错了吗?只要您处理任何可能的异常以使它们不会从方法中冒出来,这些操作就可以吗?我只是不认为在 Dispose 中做很多事情是一个好主意。我想知道社区的想法。
当从终结器中使用有问题的 dispose 方法时,您是绝对正确的。你在 Dispose 方法中应该做什么和不应该做什么的断言实际上应该被改写以应用于任何由终结器调用的东西。事实上,这通常在称为 Dispose 的方法中完成,这是一个约定问题,即 IDisposable 模式,但这些问题很容易在 Finalizer 使用的其他方法中存在。