哪个类“拥有”非托管资源(并且实现了 IDisposable)?

Posted

技术标签:

【中文标题】哪个类“拥有”非托管资源(并且实现了 IDisposable)?【英文标题】:Which class "owns" an unmanaged resource (and which implements IDisposable)? 【发布时间】:2012-03-28 07:05:57 【问题描述】:

我正在研究 an OSS project 以使流行的 MediaInfo library 在 .NET 中更易于使用,但这个问题是可以推广的。

如果派生类D在调用其基类DB的构造函数时总是实例化一个对象O。 DB 将其值设置为发送给其构造函数的值,但该值本身在 DB 的基类 B 中声明:

    谁“拥有”O(下面代码中的又名 mediaInfo)? 对于 .NET 应用程序,哪些应实现 IDisposable? 注意:O 是非托管的,或者至少是包裹在非托管库中的托管对象的实例化,但确实需要以“MediaInfo.Close(); 的形式进行清理>”。我不确定这是否算作“非托管”。

为了帮助澄清,让我使用实际代码:

D 派生自 DB

// MediaFile is "D" 
public sealed class MediaFile : GeneralStream

    public MediaFile(string filePath)
        : base(new MediaInfo(), 0) 
        // mediaInfo is "O"
        mediaInfo.Open(filePath);
    

DB设置其继承的O,派生自B

// GeneralStream is "DB"
public abstract class GeneralStream : StreamBaseClass

    public GeneralStream(MediaInfo mediaInfo, int id) 
        this.mediaInfo = mediaInfo; // declared in StreamBaseClass
        // ...
    

B 声明 O

// StreamBaseClass is "B"
public abstract class StreamBaseClass

    protected MediaInfo mediaInfo; // "O" is declared
    // ...

【问题讨论】:

不同的Stream 类型已经实现了IDisposable - 这意味着从其中一个继承的任何类都会继承这个实现。 @Oded,我认为问题中的流有所不同——它们不是从 System.IO.Stream 继承的。 @svick - 我同意,但我想我会指出 BCL Stream 和相关类型确实实现了接口。 【参考方案1】:

拥有对资源的引用的对象拥有它。

StreamBaseClass 具有引用 mediaInfo 并且应该实现 IDisposable。引用和Dispose 方法将自动被派生类继承。

【讨论】:

拥有对资源的引用并不意味着所有权。拥有对资源的only 引用确实意味着所有权(如果没有其他人拥有引用,则没有其他人可以清理它)但是在许多情况下,资源可以在几个不同的类对象之间共享;如果这些对象中的第一个是 Disposed 在资源上调用 Dispose,它会破坏任何其他使用它的人。 是的,但是 Charles 在这里向我们展示了一个类层次结构。这涉及多个类,但不一定涉及多个对象。 基类知道它在其构造函数中接收到MediaInfo,但除非构造函数的合同明确声明它承担该资源的所有权,否则它和调用者都不应该假设它应该负责处理传入的资源。接受资源作为参数的构造函数是公共的,因此虽然在这种特殊情况下它可以作为链式调用调用,但情况并非总是如此。【参考方案2】:

如果一个类 C 拥有一个变量,该变量是一个 非公开实现 IDisposable 的局部变量 V,那么 C 应该是 IDisposable 并且 C 的 IDisposable 应该处置 V。

如果类 D 拥有原生资源 N,则 D 应该是 IDisposable(删除 N)并且还应该有一个可终结的析构函数,它调用自身的 Dispose() 来释放 N。 p>

如果你遵循这个模式,那么如果你有一个 IDisposable,你应该总是在完成后 Dispose() 它,这将删除对象树下的所有内容;但如果有人忘记了你(阅读:同事、你的库的用户等)也不会泄漏任何对象,因为原生资源将被 D 的终结器清理。

【讨论】:

【参考方案3】:

IDisposable 的责任属于创建它的对象,除非另有明确约定。在资源的创建者可能不知道消费者的生命周期的情况下,通常使用相反的协议。我建议在许多情况下,当构造函数或工厂方法生成可能是传入IDisposable 的最后一个消费者时,该方法应该接受一个参数,指示它是否应该接受调用Dispose 的责任,否则接受一个回调委托,如果非空,当消费者不再需要该对象时将调用该委托。如果对象的创建者比消费者更长寿,它可以传递 null;如果创建者一旦交付对象就不再使用它,它可以传递对象的Dispose 方法。如果创建者不知道它是否会比消费者更长寿,它可以传递一个方法来确定是否仍然需要该对象,如果不需要,则调用Dispose

关于您的特定情况,在链式构造函数调用中构造 IDisposable 是资源泄漏的秘诀(因为无法将链式构造函数调用包装在 try-finally 块中)。如果您要以某种方式安全地处理它(例如,使用工厂方法而不是链式构造函数,或者使用[threadstatic] hack),我建议因为对象的创建者(派生类)将知道消费者(基类)的生命周期、所有权和清理责任应该由对象创建者承担。

【讨论】:

因此,supercat 建议 IDisposable 由创建者实现,Olivier Jacot-Descombes 建议它应该是引用所有者。我希望会有一个明确的“正确”答案,但你们俩都享有令人印象深刻的声誉并且似乎不同意。在我的特定情况下,非托管对象本质上是不可变的,这有帮助吗?虽然我还没有在代码中强制执行这一点,但使用模型是 GeneralStream 被实例化并且派生的 MediaInfo 永远不会改变。 @Charles:在任何给定时间,从创建IDisposable 到它被销毁,应该有一些对象或范围块可以被识别为负责确保@987654329 @ will 被调用,要么通过处理它本身,要么让一些其他对象或作用域块承诺处理它。在许多情况下,让对象在其构造函数中接受 IDisposable 并在其合同中同意在不再需要时对其调用 Dispose 并没有错,但这种责任承担应该记录在案,而不是猜测。跨度> 我已经采纳了您的建议(我认为)并更改了代码,以便 StreamBaseClass 既保存引用又实现 IDisposable。非托管部分打开的文件路径现在通过两个构造函数传递给 StreamBaseClass,这看起来有点不雅,但实际上减少了代码大小,因为基类负责一些以前在每个派生类中处理的初始化。谢谢你的建议!

以上是关于哪个类“拥有”非托管资源(并且实现了 IDisposable)?的主要内容,如果未能解决你的问题,请参考以下文章

托管资源与非托管资源的定义

非托管资源、IDisposable 和自定义类型

C# using()的本质

托管和非托管

使用 Core Data 时,是不是保留非托管对象类及其托管对象版本

dllimport如何在非托管dll中获取哪个应用程序调用了函数