UPDATE:我用這個問題作爲文章的基礎,可以找到here;請參閱此處以獲取有關此問題的其他討論感謝您的好問題!
雖然Schabse's answer當然是正確的,回答說,有人問這個問題,對你的問題的一個重要的變體,你沒有問:會發生什麼,如果font4 = new Font()
拋出後非託管
資源是由建設者分配的,但之前 ctor返回並填寫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
填充當且僅當需要被解除分配。
描述這樣做的技術超出了這個答案的範圍。如果你有這個要求,請諮詢專家。
它不會_leak_記憶;在最壞的情況下,它仍然會GC'd。 – SLaks
@SLaks,所以底層資源總是會被處置? 'font3.Dispose()'肯定會被調用? –
我不會感到驚訝,如果'使用(...,...)'編譯成嵌套使用塊,無論如何,但我不知道這一點。 –