如何处理我们没有引用的一次性对象?

7
如果你有一个画笔和钢笔,如下所示:
Brush b = new SolidBrush(color);
Pen p = new Pen(b);

然后像这样处理它们:

b.Dispose();
p.Dispose();

如果有以下代码:Pen p = CreatePenFromColor(color),它会为您创建画笔和笔刷,那么您该如何处置它呢?我不能在此方法内部处理掉刷子,对吗?
这是一个不适用于可处理对象的方法吗?
编辑:我的意思是,你如何处理掉BRUSH?

o.O 我不知道 Brush 和 Pen 实现了 IDisposable... - Meta-Knight
但是这个Brush实际上在哪里被引用了呢?如果你只返回一个Pen作为方法结果,那么为什么(以及在哪里)要创建它呢? - vgru
它正在执行与第一段代码相同的操作。我不知道Pen可以直接使用颜色,因为我看到的所有绘画代码都是创建刷子然后传递给Pen。 - Joan Venge
@Hank,我在问题页面上看不到你的回答,但是在个人资料页面上可以看到,为什么?它被删除了吗? - Joan Venge
5个回答

9

CreatePenFromColor方法的工作是处理Brush实例的释放。一眼看上去并不明显,但如果深入到Pen类的实现中,您会发现它并没有保留传入的Brush实例。相反,它只是用它来计算一些值。因此,Brush实例在调用CreatePenFromColor后没有存在的理由,该方法应该处理实例的释放。


这并不一定是真的。笔构造函数将刷子的句柄传递给GdipCreatePen2。我不知道GdipCreatePen2是否需要保持刷子处于活动状态(该函数几乎没有文档),但我猜测它会这样做。请记住,刷子可以是带有图像的TextureBrush,您不希望笔在内存中制作图像的单独副本,以便您可以处理刷子。 - SLaks
1
据我所知,@Slaks,GdipCreatePen2不需要它保持活动状态。我查阅了非常有限的文档,发现它只需要设置笔,而不需要维护它。 - JaredPar

6
你完成后仍需处理它。
例如,你可以这样调用它:
using (Pen p = CreatePenFromColor(color))
{
    // do something
}

如果一个方法返回一个IDisposable对象,你有责任将其释放。 [编辑]现在我明白了问题——你正在使用Pen(Brush b)构造函数。
a. 在这种情况下,似乎Pen在构造函数之后不需要Brush实例,因此你的方法可以像这样:
public Pen CreatePenFromColor(Color c)
{
    using (Brush b = new SolidBrush(c))
    { return new Pen(b); }
}
问题b. 为什么不直接使用Pen(Color color)
public Pen CreatePenFromColor(Color c)
{
    return new Pen(c);
}

c. (关于评论)如果笔在内部保留了对刷子的引用,则在您完成使用笔之前,您将无法处理它。在这种情况下,我会选择一个可以为我完成工作的类:

public class PenHelper : IDisposable
{
     private readonly Brush _brush;
     public PenHelper(Color color)
     {
         _brush = new SolidBrush(color);
     }

     public Pen CreatePen()
     {
         return new Pen(_brush);
     }

     public void Dispose()
     {
         _brush.Dispose();
     }
}

然后像这样使用它:
using (PenHelper penHelper = new PenHelper(Color.Black))
{
     using (Pen pen = penHelper.CreatePen())
     {
          // do stuff
     }
}

声明:IDisposable并未按照指南实现,而仅用于演示。此外,整个示例仅用于展示如何在需要时封装引用。当然,你应该选择Pen(color)。


为什么有人要创建笔刷来制作笔呢?!+1 - Jason Williams
谢谢。在你的例子a中,笔刷b会在Pen被返回时被释放,对吗?如果Pen内部有对笔刷b的引用,那么它会抛出一个例外?只是好奇。 - Joan Venge
感谢提供这个详细的示例。 - Joan Venge

2

你的问题没有通用的解决方案。

在你的具体例子中,这不是一个问题,因为Pen有一个直接接受Color参数的构造函数。

一些类会自行处理它们的构造函数参数(尤其是与流相关的类); 检查Reflector中的每个类。

如果你返回的类继承自Component,则可以向其Disposed事件添加处理程序。

如果你返回的类没有被封装,那么你可以创建一个继承版本,它也将释放你从中创建对象的构造函数参数。

最后,如果你真的想这样做,你可以创建一个包装类,其中包含你要返回的对象并释放构造函数参数。然而,那将非常令人困惑,我不建议这样做。


谢谢,我不知道Pen可以直接使用颜色。有趣的是,我看到很多代码分别创建画笔和笔,然后只使用笔进行绘画。 - Joan Venge
是的,确实如此。只是没有一个干净的通用解决方案。 - SLaks

1

我对许多与图形相关的类之一的不满是,它们没有处理此类问题的一致模式。真正需要的是一种实现部分引用计数的方法。这不是COM风格,其中传递引用需要不断增加引用计数,而是通过一个IDisposable图形对象,可以请求另一个共享相同底层资源的实例。资源本身将封装在具有引用计数器的共享对象中。创建另一个引用实例将增加计数器;在引用实例上调用Dispose将减少它。这将避免95%的引用计数开销,同时保留99%的好处。


0
当一个方法传递一个IDisposable实例时,它同时也将生命周期管理的责任交给了调用者。
现在,调用者有责任在使用完对象后进行处理。如果该对象包含其他IDisposable对象,则按照惯例,我们必须期望容器在我们处理它时正确地处理其子对象 - 否则这将意味着容器中存在错误。
在您的具体示例中,您应该期望Pen在处理时处理其内部Brush实例。

我认为返回对一个IDisposable对象的引用并不自动意味着你正在移交所有权。所有权的转移需要明确记录。例如,多个视图控件可能具有对可处理业务对象的引用(并侦听Disposing事件),但它们都没有拥有它。 - Wim Coenen
@wcoenen:我并不是说你不能想出一个例子,证明这不是一个可行的设计 - 我只是认为这将是一个糟糕的设计。我认为任何需要“显式文档化”任何内容的API都是失败的。 - Mark Seemann

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