在设置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,这只会导致进一步的问题。处理这种情况的实际模式是什么?

在设置IDisposable属性时调用Dispose

您可以在重新分配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。

在你的情况下,你可以采用两种策略:

  1. 在CustomGoRectangle (redPen和bluePen)之外创建2支笔,或者使它们单独的成员,并根据您的流程将其分配给Pen成员变量。
  2. 创建一个笔,当你需要它做的工作和处置它比;并删除Pen成员变量。