在设置IDisposable属性时调用Dispose
本文关键字:调用 Dispose 属性 设置 IDisposable | 更新日期: 2023-09-27 18:05:51
我今天改变了FxCop规则,指出任何未处理的IDisposables为错误,希望它可以帮助我追踪一些GDI泄漏。有趣的是,它给我指出了一个实例,我不太确定如何处理:
public class CustomGoRectangle : GoRectangle
{
public void DoStuff()
{
Pen p = new Pen(Color.Red, 4.0f);
this.Pen = p;
}
public void DoOtherStuff()
{
Pen p = new Pen(Color.Blue, 4.0f);
this.Pen = p;
}
public void Test()
{
this.Pen = Pens.Green;
DoStuff();
DoOtherStuff();
}
}
快速解释。GoRectangle是在第三方库中,并且有一个Pen属性,它不是IDisposable。
我把笔放在了很多地方,就像上面那个人为的例子。这里说明的问题是,调用DoStuff()
创建一个新笔,它永远不会被丢弃。现在我可以对这个调用dispose。如果在分配新值之前它不是空的,那么就像Test()
说明的那样,如果设置了System Pen,这只会导致进一步的问题。处理这种情况的实际模式是什么?
您可以在重新分配this.Pen.Dispose()
之前手动调用它
不,你不能处置所有的笔,因为系统笔会抛出ArgumentException
,如果你试图处置他们(我刚刚检查使用反射器)。
但是,您可以使用反射来获取Pen中的私有字段immutable
的值。如果它是false
,你可以安全地处理笔。(我不知道mono是否以同样的方式工作)
这里有一个扩展方法来帮助你:
public static void DisposeIfPossible(this Pen pen)
{
var field = pen.GetType().GetField("immutable", BindingFlags.Instance|BindingFlags.NonPublic);
if ((bool)field.GetValue(pen) == false)
pen.Dispose();
}
在分配新笔前调用
我通常是这样处理这类事情的:
1)向库提供商投诉。
2)创建一个处置包装器类,允许一个人控制当包装器被处置时,它所包装的实例是否会被实际处置。例如(忽略Dispose(bool)噪声):
public class DispositionWrapper<T> : IDisposable
where T : IDisposable
{
private readonly T _instance;
private bool _allowDisposition;
public DispositionWrapper(T instance, bool allowDisposition)
{
if (instance == null)
{
throw new ArgumentNullException("instance");
}
this._instance = instance;
this._allowDisposition = allowDisposition;
}
public T Instance
{
get
{
return this._instance;
}
}
public void Dispose()
{
if (this._allowDisposition)
{
this._instance.Dispose();
}
}
}
3)使用处置包装器允许对已知允许处置的实例进行早期清理。例如:
public class CustomGoRectangle : GoRectangle, IDisposable
{
private DispositionWrapper<Pen> _ownedPen;
public override Pen Pen
{
get
{
return this._ownedPen.Instance;
}
set
{
if (value == null)
{
this.OwnedPen = null;
}
else
{
this.OwnedPen = new DispositionWrapper<Pen>(value, false);
}
}
}
private DispositionWrapper<Pen> OwnedPen
{
get
{
return this._ownedPen;
}
set
{
if (this._ownedPen != null)
{
this._ownedPen.Dispose();
}
this._ownedPen = value;
}
}
public void DoStuff()
{
this.OwnedPen = new DispositionWrapper<Pen>(new Pen(Color.Red, 4.0f), true);
}
public void DoOtherStuff()
{
this.OwnedPen = new DispositionWrapper<Pen>(new Pen(Color.Blue, 4.0f), true);
}
public void Test()
{
this.OwnedPen = new DispositionWrapper<Pen>(Pens.Green, false);
this.DoStuff();
this.DoOtherStuff();
}
public void Dispose()
{
if (this.OwnedPen != null)
{
this.OwnedPen.Dispose();
}
}
}
不幸的是,这意味着通过Pen属性分配的Pen实例在完成之前不会被清理。如果您担心这一点,您可能需要考虑扩展配置包装器,以检测是否可以通过反射读取其不可变字段的值来清除Pen,正如jgauffin的回答所提到的。
对于一个IDisposable对象需要使用另一个对象的情况,理想的方法是有一个参数,该参数指示一个对象是否应该获得IDisposable对象的引用。一个对象坚持要获得传入的IDisposable(例如StreamReader)的所有权,就像一个从不接受所有权的对象一样令人讨厌。如果GoRectangle不接受所有权,那么创建用于GoRectangle的笔的人将负责处置它。假设任何非不可变笔都应该被处置的方法是危险的,因为不能保证被丢弃的GoRectangle所使用的任何笔不会被仍然在作用域内的其他对象共享。
任何一次性对象在不需要时都应该手动处理,而不是依赖GC。
在你的情况下,你可以采用两种策略:
- 在CustomGoRectangle (redPen和bluePen)之外创建2支笔,或者使它们单独的成员,并根据您的流程将其分配给Pen成员变量。
- 创建一个笔,当你需要它做的工作和处置它比;并删除Pen成员变量。