C#内存分配和释放模式

8
由于C#使用垃圾回收机制,那么何时需要使用.Dispose方法来释放内存呢?
我知道有几种情况,我会尽量列举出来。
1.如果我关闭包含GUI类型对象的窗体,那么这些对象是否被取消引用并因此被收集?
2.如果我使用new创建一个本地对象,应该在方法退出之前.Dispose它,还是让GC处理它?在这种情况下,什么是好的实践?
3.有没有任何情况可以理解为强制进行GC?
4.当对象被收集时,事件是否也被GC收集?

可能是 http://stackoverflow.com/questions/2714811 的重复问题,有没有一种常见的做法可以使垃圾收集器更容易释放内存? - George Stocker
此外,重复的问题:https://dev59.com/M3RC5IYBdhLWcg3wVvct 另请参阅:https://dev59.com/TUjSa4cB1Zd3GeqPEE_2 https://dev59.com/cXI-5IYBdhLWcg3wsKh4 https://dev59.com/CHVC5IYBdhLWcg3wbghT https://dev59.com/CHVC5IYBdhLWcg3wbghT https://dev59.com/VkfRa4cB1Zd3GeqP_KoL https://dev59.com/4nRC5IYBdhLWcg3wOeSB - George Stocker
通常,IDisposable接口用于处理在对象被垃圾回收器清理时无法返回、关闭或以最佳方式使用的非托管资源 - 托管对象总是由垃圾回收器清理。请参阅IDisposable @ http://msdn.microsoft.com/en-us/library/system.idisposable.aspx 对于第2点,在托管代码中,无论您是否调用Dispose(),垃圾回收器都会处理对象。无论您是否调用Dispose(),#1和内置的“GUI类型对象”都将被收集。还请参阅http://msdn.microsoft.com/en-us/magazine/bb985010.aspx - John K
1
@George:我不认为它们是来自任何一个重复的。或许我应该澄清一下,我的主要问题是什么时候应该显式释放内存,何时不必担心它。答案似乎很明确,即始终Dispose对象(如果可以Disposed),这是首选的模式。 - Neal
5个回答

10

理论上,如果您正确定义了组件,则永远不需要在对象上调用Dispose(),因为终结器最终会处理它。

虽然如此,但每当您使用实现IDisposable的对象时,最好在完成工作后立即调用Dispose()。

对于一些特定情况:

1)如果您知道已经“完成”了窗体,可以调用Dispose()。这将在与窗体关联的非托管资源的那个时间点执行清理。

2)在这种情况下:如果您的对象只在该方法中使用,请改用"using":

using (MyObject myObject = new MyObject())
{
   // use your object
} // It'll be disposed of here for you

3) 通常情况下不建议这样做,除非有特殊原因。

4) 事件是委托的一种形式 - 委托相关的内存会在委托本身成为未引用对象时被回收,而这通常发生在涉及的对象变成未引用对象时。


我会使用using() {}模式处理所有IO类型资源。我将开始将其用于所有可处理对象,以遵循一致的模式。感谢您的答案。 - Neal

3
你应该在实现IDisposable接口的每个类上调用Dispose方法。如果它不需要被Dispose,那么它就不会实现IDisposable接口。
至于你的其他问题:
1.当你将控件添加到窗体的Controls集合中时,当窗体关闭时,该控件将自动被释放,所以你不需要做任何操作。
2.如果对象实现了IDisposable接口,则需要调用Dispose方法。例如,如果你使用new FileStream(...)创建了一个FileStream对象,则需要释放FileStream对象,因为它实现了IDisposable接口。我建议你阅读C#中的using构造函数,它可以更容易地处理IDisposable对象。
3.99.99%的情况下,垃圾回收器会知道最佳运行时间。这是一种“你需要它时你就会知道”的情况。
4.当包含事件的对象不再被引用时,逻辑上,事件中包含的任何对象引用也不再被引用,并且可以被收集。

#1: 如果对象在设计时添加但未添加到控件集合中,那么它仍然会在窗体关闭时被释放吗? #2: 实际上,我几乎总是在处理IO资源时实现using(){}模式。 #4: 我已经阅读了许多情况,其中事件而不是属性的删除将“泄漏”内存。或者说没有释放内存。我意识到这可能只是一个可以忽略的引用。 - Neal
在#4中,当包含事件的对象不再被引用时,事件本身所持有的对象引用也将不再被引用。问题出现在向事件添加处理程序时,事件会保留对该对象的引用:因此,除非回收事件或手动取消订阅,否则该对象将不会被回收。对于大多数WinForms内容,事件处理程序通常已经在包含窗体中,因此通常不是问题。 - Dean Harding

2

看这个问题:

有没有一种常见的做法可以使.NET中垃圾回收器更容易释放内存?

如果您的类实例化了IDisposable接口,那么它可能意味着它具有必须直接处理的系统资源。 一种简单的方法是使用using关键字,如下所示:

using(var g = Graphics.FromBitmap(bmp))
{
    //Do some stuff with the graphics object
}

根据我提到的那个问题中@Matt S的答案。

针对您的问题:

  1. 如果您实例化具有IDisposable的对象,则需要在关闭窗体时将其处理掉。这在Winforms中很简单,因为Winforms对话框有Dispose方法,但在WPF中很棘手。对于WPF,我通过保留WPF类但隐藏它,调用一个处理所有对象(如串口)的dispose方法,然后将WPF类设置为空来解决该问题。
  2. 不需要,让垃圾回收器来处理它。
  3. 我认为是这样,但我被负面投票了 :) 当我进行非常大的分配时,强制GC将它们删除是一个好主意。
  4. 我不确定。我认为事件本身就是对象,因此在不再使用时会被收集。

你的第二个回答似乎与其他人的答案相矛盾。为什么你会说让垃圾回收器来处理它?同时,这似乎与你最初的陈述有些冲突? - Neal
如果你只是使用new创建一个对象,但它没有实现IDisposable接口,那就让垃圾回收器来处理它。但是,如果它实现了IDisposable接口,那么在释放它之前,你必须先处理它,按照第一条规则。如果你声明一个新的int数组,int类型不实现IDisposable接口,所以你可以让垃圾回收器来处理它。 - mmr

1

就像Reed Copsey所说的那样,通常不必调用Dispose。

可能会导致您问题的一个可能情况是,静态对象持有其他不再在任何其他地方使用的对象的引用。以下代码显示了一个示例:

Form_Load(...)
    MyState.Instance.AddressChanged += this.User_AddressChanged;
End

如果由于某种原因,在表单卸载时,代码未注销事件处理程序,则状态对象仍将引用表单实例。

0
如果您正在使用一个 IDisposable 对象,考虑使用 using 语句来自动处理对其的释放。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接