最小 ID仅对托管资源具有可处理的影响
本文关键字:可处理 影响 资源 ID 最小 | 更新日期: 2023-09-27 18:32:41
关于用于处置非托管资源的"标准完整"IDisposable
实现的信息 - 但实际上这种情况(非常(罕见(大多数资源已经被托管类包装(。 此问题重点介绍 IDisposable 的模拟实现,适用于更常见的"仅托管资源"情况。
1:下面代码中IDisposable
的mimimal实现是否正确,有问题吗?
2:有什么理由在所呈现的最小暗示之上添加完整的标准IDisposable
实现(Dispose()
、Dispose(bool)
、Finalizer
等(?
3:在这种最小的情况下,将Dispose
设为虚拟(因为我们不提供Dispose(bool)
(是否可以/明智?
4:如果这个最小的实现被替换为一个完整的标准实现,其中包括一个(在这种情况下是无用的(终结器 - 这是否会改变 GC 处理对象的方式?有什么缺点吗?
5:该示例包括Timer
和事件处理程序,因为这些情况特别重要,不容错过,因为未能释放它们将使对象保持活动状态和踢(this
在Timer
的情况下,eventSource
在事件处理程序的情况下(,直到GC在其时间内处理它们。还有其他类似的例子吗?
class A : IDisposable {
private Timer timer;
public A(MyEventSource eventSource) {
eventSource += Handler
}
private void Handler(object source, EventArgs args) { ... }
public virtual void Dispose() {
timer.Dispose();
if (eventSource != null)
eventSource -= Handler;
}
}
class B : A, IDisposable {
private TcpClient tpcClient;
public override void Dispose() {
(tcpClient as IDispose).Dispose();
base.Dispose();
}
}
裁判:
MSDN
SO:何时需要管理托管资源
SO:如何在 C#
中在 Dispose(( 方法中释放托管资源SO: Dispose(( 用于清理托管资源
-
实现是正确的,没有问题,前提是没有派生类直接拥有非托管资源。
-
实现完整模式的一个很好的理由是"最小意外原则"。 由于 MSDN 中没有权威文档描述这种更简单的模式,因此维护开发人员可能会有疑问 - 即使您觉得有必要询问 StackOverflow :)
是 的,在这种情况下,"处置"是虚拟的。
如果已调用 Dispose 并正确实现(即调用 GC。抑制完成(
.NET Framework 本身之外的绝大多数IDisposable
类都是IDisposable
的,因为它们拥有托管IDisposable
资源。 他们很少直接持有非托管资源 - 仅当使用 P/Invoke 访问 .NET Framework 未公开的非托管资源时,才会发生这种情况。
因此,推广这种更简单的模式可能是一个很好的论据:
- 在
极少数情况下,使用非托管资源时,应将它们包装在实现终结器(如 SafeHandle(的密封
IDisposable
包装器类中。因为它是密封的,所以此类不需要完整的 IDisposable 模式。在所有其他情况下,绝大多数情况下,可以使用更简单的模式。
但是,除非Microsoft或其他权威来源积极推广它,否则我将继续使用完整的IDisposable
模式。
另一种选择是重构代码以避免继承并使IDisposable
类密封。 那么更简单的模式很容易证明是合理的,因为不再需要支持可能的继承的尴尬回旋。 就我个人而言,我大部分时间都采用这种方法;在极少数情况下,我想使非密封类成为一次性的,我只是遵循"标准"模式。 培养这种方法的一个好处是,它倾向于将你推向组合而不是继承,这通常使代码更容易维护和测试。
我推荐的Dispose
模式是将非虚拟Dispose
实现链接到虚拟void Dispose(bool)
,最好是在以下之后:
int _disposed;
public bool Disposed { return _disposed != 0; }
void Dispose()
{
if (System.Threading.Interlocked.Exchange(ref _disposed, 1) != 0)
Dispose(true);
GC.SuppressFinalize(this); // In case our object holds references to *managed* resources
}
使用此方法将确保Dispose(bool)
只被调用一次,即使多个线程尝试同时调用它也是如此。 尽管这种同时处置尝试很少见(*(,但防范它们的成本很低;如果基类不执行上述操作,则每个派生类都必须具有自己的冗余双释放保护逻辑,并且可能还有一个冗余标志。
(*( 一些通信类大多是单线程的,并且使用阻塞 I/O,允许从任何线程上下文调用Dispose
来取消阻塞其自身线程的 I/O 操作 [显然Dispose
不能在该线程上调用,因为该线程在被阻塞时无法执行任何操作]。 对于这样的对象或封装它们的对象来说,让外部线程尝试Dispose
它们作为在它们将被主线程处置的那一刻中止其当前操作的一种手段,这是完全可能的——也不是不合理的。 同时Dispose
调用可能很少见,但它们的可能性并不表示任何"设计问题",前提是Dispose
代码可以对一个调用进行操作并忽略另一个调用。