UPDATE:我用这个问题作为可以找到一篇文章的基础在这里 ; 看到它以进一步讨论此问题。感谢您的好问题!
尽管Schabse的答案当然是正确的,并且可以回答所提出的问题,但是您没有提出的问题有一个重要的变体:
如果在构造函数分配了非托管资源之后但在 ctor返回并填充引用之前font4 = new Font()
抛出该异常怎么办?font4
让我更清楚一点。假设我们有:
public sealed class Foo : IDisposable
{
private int handle = 0;
private bool disposed = false;
public Foo()
{
Blah1();
int x = AllocateResource();
Blah2();
this.handle = x;
Blah3();
}
~Foo()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (this.handle != 0)
DeallocateResource(this.handle);
this.handle = 0;
this.disposed = true;
}
}
}
现在我们有
using(Foo foo = new Foo())
Whatever(foo);
这和
{
Foo foo = new Foo();
try
{
Whatever(foo);
}
finally
{
IDisposable d = foo as IDisposable;
if (d != null)
d.Dispose();
}
}
好。假设Whatever
抛出。然后,该finally
块运行,资源被释放。没问题。
假设Blah1()
抛出。然后在分配资源之前发生抛出。对象已分配,但ctor永不返回,因此foo
也永不填写。我们从未输入,try
因此也从未输入其中finally
一个。对象引用已被孤立。最终,GC将发现该问题并将其放入终结器队列中。 handle
仍然为零,因此终结器不执行任何操作。 请注意,终结器必须要面对要终结的,其构造函数从未完成的对象时才具有鲁棒性。您需要编写强大的终结器。这是为什么您应该将定稿编写器留给专家而不是自己尝试完成的另一个原因。
假设Blah3()
抛出。在分配资源之后发生抛出。但是同样,foo
永远不会被填充,我们永远不会输入finally
,并且终结器线程会清除对象。这次,句柄非零,终结器将其清除。同样,终结器在其构造函数从未成功执行过的对象上运行,但是终结器仍在运行。显然,这一定是因为这一次,它有工作要做。
现在假设Blah2()
抛出。抛出发生在分配资源之后但未填充之前 handle
!同样,终结器将运行,但现在handle
仍然为零,并且我们泄漏了句柄!
您需要编写非常聪明的代码,以防止发生这种泄漏。现在,就您的Font
资源而言,到底谁在乎?我们泄漏了一个字体句柄,这很重要。但是,如果你绝对肯定需要的是每一个非托管资源清理无论什么异常的时机,那么你有你的手一个非常棘手的问题。
CLR必须使用锁来解决此问题。从C#4开始,使用该lock
语句的锁已实现如下:
bool lockEntered = false;
object lockObject = whatever;
try
{
Monitor.Enter(lockObject, ref lockEntered);
lock body here
}
finally
{
if (lockEntered) Monitor.Exit(lockObject);
}
Enter
我们已经非常仔细地编写了代码,因此无论抛出了什么异常,当且仅当实际使用了锁时才将其lockEntered
设置为true 。如果您有类似的要求,那么实际需要编写的是:
public Foo()
{
Blah1();
AllocateResource(ref handle);
Blah2();
Blah3();
}
并AllocateResource
像Monitor.Enter
这样巧妙地编写,以便无论内部发生什么情况AllocateResource
,只要且仅当需要重新分配时,才进行handle
填充。
描述这样做的技术超出了此答案的范围。如果您有此要求,请咨询专家。