2010-03-30 89 views
28

我不能相信我仍然对此感到困惑,但是,以任何方式,让我们终于明白它:我是否需要在托管对象上调用Dispose()?

我有一个类覆盖了OnPaint做一些绘图。为了加快速度,我在构造函数中创建了笔,画笔等,以便OnPaint不需要继续创建和处理它们。

现在,我要确保我总是处置这类物品,但我有一种感觉,我不需要,因为,尽管他们实现IDisposable,他们管理的对象。

这是正确的吗?


感谢所有的答案,这个问题已经确定了。
我很高兴我一直在使用'使用'一直保持警惕,所以我不需要通过我所有的代码检查。我只是想清楚,我不是一个毫无意义的用户。另外,我确实有一个奇怪的情况,最近,我不得不更换一个使用块并手动调用dispose!我会挖掘出来,并创建一个新的问题。

+0

能否请您为具有明确调用Dispose替换“使用”块提供详情(或链接)?我很好奇看到那里发生了什么。 – 2016-10-14 14:47:33

回答

43

它是而不是正确。您需要处理实现IDisposable的对象。这就是他们实施IDisposable的原因 - 指定它们(直接或间接)包装非托管资源的事实。

在这种情况下,非托管资源是GDI手柄,如果你无法处理它们时,你实际上是与他们做,你会泄漏的句柄。现在这些特定对象有终结器这将导致资源在GC启动时被释放,但您无法知道什么时候会发生。从现在开始可能是10秒,可能是从现在开始的10天;如果您的应用程序没有产生足够的内存压力导致GC启动并在这些画笔/笔/字体等上运行终结器,那么在GC意识到发生了什么之前,最终可能导致GDI资源的操作系统匮乏。

此外,您不能保证每个非托管包装实际上都实现了终结器。 .NET框架本身是在类实现IDisposablecorrect pattern实现它的意义相当一致的,但它是完全可能的一些其他类有一个破碎的实现,不包括终结,因此不收拾妥当除非明确调用Dispose。一般来说,IDisposable的目的是你不应该知道或关心具体的实现细节;相反,如果它是一次性的,那么你就可以把它处置掉。

道德故事:总是处置IDisposable对象。如果您的课程“拥有”IDisposable的对象,则它应该自行实施IDisposable

+0

我可能会将终结器称为“理想”模式,但在很多情况下,IDisposable对象不能有效地实现终结器,例如,他们在哪里使用线程静态变量或来自未知对象的事件订阅。终结器在与其他代码不同的线程上下文中运行,这将很难访问与在另一个线程上创建的废弃IDisposable关联的线程静态变量。虽然可以设计终结器将事件解除挂钩,但大多数事件在这种情况下无法安全地解除挂钩。 – supercat 2011-10-18 13:11:05

+1

@supercat:这是一个非常危险的设计,将非托管资源存储在线程本地存储中。这足以说明,终结器可能会运行在不同的线程中,但是如何保证'Dispose'将从导致资源初始化的相同线程中调用?我可以想到几种情况,情况并非如此。老实说,不要使用ThreadStatic来处理这种事情,请将关键资源保存在可追踪它们的地方。 – Aaronaught 2011-10-19 00:44:39

+0

线程本地存储中的项目往往是*本身*,非托管资源;如果创建它们的代码在不清除它们的情况下放弃控制权,但该线程仍然存在(可能在线程池中),则这些项目可能永远无法清理。使用IDisposable进行这些操作可以使用“使用”块来管理它们,这些块本质上会正确处理线程。 – supercat 2011-10-19 04:20:59

9

你需要处理它们。

管理对象由自己管理自己内存。但内存不是对象使用的唯一资源。 Dispose()旨在释放其他资源。

1

不,IDisposable用于使用非托管资源的托管对象。通常情况下,你应该总是在完成后处置它们。

1

你真的需要来查找文档中的画笔,钢笔等

如果他们不使用非托管资源,您可能没有调用Dispose。但是使用/配置模式有时会被“误用”。作为一个例子,考虑一下ASP.NET MVC框架。在这里,你可以写这样的:

using(Html.BeginForm(...)){ 
    ///HTML input fields etc. 
} 

Html.BeginForm(...)被调用,一个FORM标签将被输出。当使用语句结束时,将从Html.BeginForm(...)返回的对象上调用Dispose。调用Dispose会导致结束FORM标记被渲染。通过这种方式,编译器实际上会强制执行FORM标签的配对,所以您不会忘记结束标签。

2

你是否对此进行了分析,看是否创建&处置这些对象确实是一个问题?我不认为这是。

通过使用“创建使用块”模式,您可以让事情变得更轻松,并且肯定不会出错。

如果您确实想要创建它们一次,那么还要在您拥有的类上实现IDisposable,并将Dispose重复用于您拥有的对象。不需要析构函数(终结器)。

这样做对于实际上不需要处理的对象几乎没有成本处置,但是如果您忘记在需要处理的对象上丢弃Dispose,则会产生很大的成本。

1

不,Pen s和Brush es是不是完全托管对象。

它们包含非托管资源的句柄,即底层图形系统中相应的GDI对象。 (不确定这里的确切术语......)

如果你不处理它们,句柄将不会被释放,直到对象被垃圾回收器完成,并且不能保证它很快就会发生,或者根本没有。

3

我写了一个GDI +图表组件,它使用了大量的笔和画笔。我创建了它们,并将它们放置在执行绘制的代码块中,并且性能从来都不是问题。更好的是,然后有一个长寿的手柄挂在操作系统恕我直言。

1

没有错。我同意Aaronaught。另外,微软建议,在Don Box发布的2003年中期网络广播中,每个.Net开发者都应该处理他们自己的对象,无论是托管的还是非托管的,因为这可以将代码性能提高20%。如果做得对,它可以大大提高性能。因此,它是每个.NET开发人员都需要知道和使用的核心技能。

5

你的方法有一个明显的讽刺。通过预先创建笔/画笔,您正是创建 Dispose()试图解决的问题。那些GDI对象将会更长,就像您不调用Dispose()时那样。实际上更糟糕的是,他们至少会在表格关闭之前。

他们可能在足够长的时间才能晋升到第二代。垃圾收集器不会经常执行第2代收集,现在是更多对他们调用Dispose()很重要。通过将表单的Dispose()方法从Designer.cs文件移动到form.cs文件并添加Dispose调用来完成此操作。

但是,这样做是正确的。钢笔和画笔是非常便宜的物品。在Paint事件中创建它们。然后使用using语句,以便它们立即得到处理。使用秒表类重新确保这实际上不会导致任何放缓。

1

其他人提到“使用”为GDI对象块 - 在这里是一个代码示例:

using(var bm = new Bitmap()) 
using(var brush = new Brush()) 
{ 

    // code that uses the GDI objects goes here 
    ... 

} // objects are automatically disposed here, even if there's an exception 

注意,只要你想为一个单一的代码块,你可以有许多“使用”线。

我认为这是一个很好的,干净的方式来处理一次性对象。

0

虽然你问笔和笔刷,字体是一个奇怪的怪类。特别是,如果为设置控件的Font属性创建字体,则仍然需要负责处理该字体 - 所有权不会转移到控件 - 但可以通过将字体置于任何时候 - 即使在字体创建完成之后,即可将其分配给控件。看起来Font是托管信息对象和非托管GDI资源的组合,出于某种目的只有前者是必需的。奇怪的设计 - 字体应该是两个类。

相关问题