为什么析构函数不足以实现IDisposable?

时间:2014-01-21 13:24:35

标签: c# destructor idisposable

考虑一下我们正在使用一些非托管资源。

最常见的方法是:

//With IDisposable
using (MemoryStream memoryStream = new MemoryStream())
{
   //Operate with memory stream
}

但是我们不能写下面的内容吗?

//With destructor called at the end of a block
{
    MemoryStream memoryStream = new MemoryStream();
    //Operate with memory stream
}
  • IDisposable的主要目标是什么?

  • 它只是终结逻辑分离的设计模式吗?

  • 例如, Java 会提供类似的内容吗?

5 个答案:

答案 0 :(得分:2)

析构函数不会在块结束时被调用,但每当GC破坏对象时。您无法控制何时发生这种情况。有时你需要那种控制。那是IDisposable发挥作用的时候。

答案 1 :(得分:2)

  
      
  • IDisposable的主要目标是什么?

  •   
  • 它只是终结逻辑分离的设计模式吗?

  •   

它的目标只是成为调用该逻辑的标准接口。 recommended implementation将手动处理(Dispose(true))与来自终结器(Dispose(false))的Dispose分开只是为了提高效率 - 从终结器中处理内部字段是多余的,因为终结器可以'我们手动调用,我们知道他们必须已经被收集了。

需要手动处理,因为垃圾收集不是立竿见影的(并且您不能单独强制收集单个对象)。这不仅仅是一个效率问题,因为非托管资源可能不支持多个访问,因此如果第一个尚未收集,则稍后访问它的另一个对象将失败。甚至不保证收集,因为正如Ricibob所示,将代码放在显式范围内并不会阻止外部对象获得自己的引用。

  
      
  • 例如, Java ,提供类似的东西吗?
  •   

using只是一个自动实现的“try ... finally”块,它调用预定义的方法。

Java 将此功能包含为extension of the try statement itself。这与using的工作方式相同,不同之处在于它还允许您添加自己的catchfinally块,而无需将其包含在其他try块中。

Python context managers,这是一个更灵活的版本。上下文管理器可以定义特定的异常处理以及finally,并且可以返回与传入的不同的对象 - 也就是说,这是可能的:

with CustomDisposer(MemoryStream()) as memoryStream:

其中CustomDisposer对象负责dispose实现,但它返回MemoryStream作为要分配给memoryStream变量的资源。

Ruby 有一个yield语句,允许函数wrap a code block并可选地为块提供参数,因此您可以通过将给定对象传递给阻止然后在ensurefinally等效物)中调用dispose:

def using(o)
    yield o
ensure
    o.dispose
end

using MemoryStream.new do |memoryStream|
    #Operate with memory stream
end

当然,由于这是函数定义的一部分,因此不需要专用的using函数 - 例如,它可以直接在MemoryStream.Open方法中实现。

答案 2 :(得分:1)

考虑:

var sc = new StreamConsumer();
{
    var memoryStream = new MemoryStream();
    //Operate with memory stream
    sc.Stream = memoryStream;
}
sc.DoStuffWithStream();

这是GC作业确定我们何时实际使用了memoryStream并且可以销毁它。这并不总是很明确(因为C ++开发人员都知道......)我们不知道GC何时会这样做 - 它不是确定性的。

使用/ IDisposable不是关于对象销毁,而是关于以及时确定的方式控制对象对资源的访问。当我们应用一个使用时,我说在这个语句之外我知道资源(文件,内存映射文件,套接字等)是免费的,并且可以被其他对象使用。如果我尝试访问一个已处置的对象(在其使用范围之外),因为还有其他对象持有refs - 那么我将得到一个例外。

答案 3 :(得分:1)

同样重要的是要注意终结器(descructor)实际上并不能保证完全运行。通常在实现终结器时,它所做的就是调用Dispose,以解决消费者忘记将对象放入使用块的可能性。如果您怀疑某个对象的内存泄漏(如果没有丢弃),则可以实现此模式。

Microsoft甚至将这种做法命名为:http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx

要点是:在C#中,你应该几乎总是使用IDisposable。

答案 4 :(得分:0)

GC的基本目的是确保对任何对象的引用都不会变成除引用该对象之外的任何内容。因此,它必须保持对象,如果它可以看到任何可能的方式,通过它可以访问它们的引用。因为存在强根源引用的对象通常是有用的#34;并且没有强根带引用的对象通常不是“有用的”#34;垃圾收集器也可以在某种程度上识别对象这些都是无用的,应该消除对它们的所有引用。

不幸的是,即使GC完全响应并且可以立即识别出对任何给定对象的最后一个强引用何时消失,这并不意味着它可以可靠地识别无用的对象。如果X拥有对Y的强引用,那通常意味着如果某些东西对X感兴趣,则某些东西会对Y感兴趣。但是,在某些情况下,它代表Y有兴趣从X接收某种通知。假设X从静态定时器 - 滴答处理程序注册接收定时器 - 定时器事件,并且每次从X接收事件并且某些条件为真时Y递增Int64计数器。引用该计数器的其他对象可以检查计数器的状态。只要对X的某些引用存在而不是X 所持有的那个,则定时器事件应该继续触发,并且X应该继续更新Y的计数器。一旦所有其他对Y的引用都不存在,Y的计数器将毫无用处,Y的更新努力也是如此。这将使X的通知无效,这将使X接收的静态计时器事件无效。应该释放使用的计时器资源,并从静态处理程序中分离出X;然后X和Y应该不复存在。除了一个问题之外,所有这些都非常好且有序:即使X的有用性取决于Y的有用性,并且计时器的有用性取决于Y的有用性,对象引用指向另一种方式。虽然可以安排一些事情,以便在Y被放弃时定时器将被释放,但这样做会增加使用定时器时需要运行的代码的大量开销。

相关问题