C# 图像资源存储器
由于上个月我在一个不太明显的非托管资源上遇到了一个非常严重的问题,我对内存泄漏问题有点紧张。 我只是在一个非常简单的测试应用程序上编写代码,上面有一个按钮,上面有两张不同的图片,我注意到我不太确定我是否有“问题” 如果我有两个图片资源Pic1和Pic2以及一个ImageButton对象,它只是从UserControl继承的某个对象,并且具有更改的OnPaint:C# 图像资源存储器,c#,.net,user-interface,memory-leaks,C#,.net,User Interface,Memory Leaks,由于上个月我在一个不太明显的非托管资源上遇到了一个非常严重的问题,我对内存泄漏问题有点紧张。 我只是在一个非常简单的测试应用程序上编写代码,上面有一个按钮,上面有两张不同的图片,我注意到我不太确定我是否有“问题” 如果我有两个图片资源Pic1和Pic2以及一个ImageButton对象,它只是从UserControl继承的某个对象,并且具有更改的OnPaint: protected override void OnPaint(PaintEventArgs e) { base.OnPaint
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
//stuff
if (this.keyStatus))
{ this.imageButton.DefaultImage = Resource1.Pic1; }
else
{ this.imageButton.DefaultImage = Resource1.Pic2; }
e.Graphics.DrawImage(this.defaultImage, this.ClientRectangle);
}
除了OnPaint不是分配DefaultImage的好地方之外(它只是在这里用一段简短的代码向您展示我的意思),我只是在这里分配对我的预编译资源的引用,是吗?我不会像使用新位图(Resource1.Pic1)
调用它那样创建副本。
因此,如果我每5秒钟更改一次keyStatus,我的屏幕上就会出现一张非常恼人的图片,而且会有很多更改,但没有问题,因为图片的更改或不可见会不时泄漏内存。对吗
非常感谢 对象引用如何工作 假设你有一个随机对象。对象是类类型(不是值类型)且不是
IDisposable
。基本上,这意味着:
// y is some object
var x = y;
现在,x没有从y复制所有数据,只是对y的内容进行了一个新的引用。简单
为了确保不会出现内存泄漏,GC会跟踪所有对象,并(定期)检查哪些对象是可访问的。如果一个对象仍然是可访问的,它将不会被删除-如果它不是,它将被删除
然后是非托管代码
只要您坚持托管代码,一切都很好。当您遇到非托管代码时(比如:GDI+,它是许多System.Drawing东西的本机对应项),您需要做额外的簿记来摆脱代码。毕竟,.NET运行时对非托管数据了解不多——它只知道有一个指针。因此,GC将清除指针,而不是数据——这将导致内存泄漏
因此,来自.NET的家伙添加了IDisposable
。通过实现IDisposable,您可以实现额外(非托管)清理,例如释放非托管内存、关闭文件、关闭套接字等
现在,GC知道终结器,它是作为一次性模式的一部分实现的(详细信息:)。但是,您通常不希望等待GC运行来清理非托管资源。因此,当对象可以清理并且具有非托管资源时,通常最好调用Dispose()
与System.Drawing.Bitmap
一样,它实现了IDisposable
在大多数情况下,您可以简单地使用语句将IDisposable
包装在中,这将在一个漂亮的try/finally子句中为您调用'Dispose()。e、 g:
using (var myBitmap = new Bitmap(...))
{
// use myBitmap
}
// myBitmap including all resources are gone.
资源位图呢
@HansPassant指出,每次访问位图属性时,资源位图都会生成一个新的位图。这基本上意味着位图被复制并需要处理
换言之:
// Free the old bitmap if it exists:
if (this.imageButton.DefaultImage != null)
{
this.imageButton.DefaultImage.Dispose();
this.imageButton.DefaultImage = null;
}
// assign new imageButton.DefaultImage
因此,这解决了内存泄漏问题,但会为您提供大量复制的数据
如果您不想处置
下面是为什么我对Hans的评论感到惊讶的部分:)基本上,每次都给按钮分配一个位图,所以你不想一遍又一遍地复制数据——这没有什么意义
因此,您可能会想到将资源包装到一个“静态”容器中,而根本不释放它:
static Bitmap myPic1 = Resource1.Pic1;
static Bitmap myPic2 = Resource1.Pic2;
...
if (this.keyStatus))
{
this.imageButton.DefaultImage = myPic1;
}
else
{
this.imageButton.DefaultImage = myPic2;
}
这是可行的,但如果您在某个时候决定生成图像,则会给您带来问题。为了举例说明,假设我们更改代码如下:
if (this.keyStatus))
{
this.imageButton.DefaultImage = myPic1; // #1 don't dispose
}
else
{
Bitmap myPic3 = CreateFancyBitmap(); // #2 do dispose
this.imageButton.DefaultImage = myPic3;
}
现在,这里的问题是组合myPic1
是一个静态对象,不应丢弃。另一方面,myPic3
不是,应该被处置。如果调用Dispose()
,将在#1处出现严重异常,因为数据不再存在。没有合适的方法来区分这两者 资源设计器在生成正确代码方面做得不是很好。Pic1
和Pic2
属性应该是方法。每次使用该属性时,它们都会返回一个新位图,该位图应在使用后进行处理。因此,在这段非常糟糕的代码片段中,如果imageButton.DefaultImage不为null,则很可能必须处理它。通常,无法100%保证此图像不是来自其他地方,因此可能会在其他地方使用。很难对错误代码进行评论。@HansPassant What。。什么?好的,谢谢你的提醒,这改变了一切。我会改变我的答案;Zorakh似乎不明白你的意思。嗨,谢谢你的回答,虽然我不太确定你说的Pic1和Pic2是返回位图新实例的方法是什么意思。我特别将它们作为资源包含在我的项目中,以便每次都使用相同(且仅限于)的副本,而不需要在途中处理实例。如果我得到了atlaste的正确答案,这里确实是这样,所以我不必在这里担心这个问题(事实上我没有观察到内存问题)。或者在我上面的示例中是这样的,但是按照你说的做会更好吗?那样的话:你能告诉我它的优点吗?谢谢!您好,非常感谢,这就是为什么我要问,有很多IDispobles在周围,但在这个非常简单的情况下,我并不觉得我真的需要处理一些东西,因为我只是传递了这个参考。你明白Hans Passant在评论中所说的Pic1和Pic2是返回新位图的方法吗?很明显,在这种情况下我需要处理,但我们不是同意这里不是这样吗?@Zorakh我想我的最新答案回答了你的问题?