正確實(shí)現(xiàn) IDisposable .NET中用于釋放對(duì)象資源的接口是IDisposable,但是這個(gè)接口的實(shí)現(xiàn)還是比較有講究的,此外還有Finalize和Close兩個(gè)函數(shù)。 MSDN建議按照下面的模式實(shí)現(xiàn)IDisposable接口: 1 public class Foo: IDisposable
2 { 3 public void Dispose() 4 { 5 Dispose(true); 6 GC.SuppressFinalize(this); 7 } 8 9 protected virtual void Dispose(bool disposing) 10 { 11 if (!m_disposed) 12 { 13 if (disposing) 14 { 15 // Release managed resources 16 } 17 18 // Release unmanaged resources 19 20 m_disposed = true; 21 } 22 } 23 24 ~Foo() 25 { 26 Dispose(false); 27 } 28 29 private bool m_disposed; 30 } 31 32
在.NET的對(duì)象中實(shí)際上有兩個(gè)用于釋放資源的函數(shù):Dispose和Finalize。Finalize的目的是用于釋放非托管的資源,而Dispose是用于釋放所有資源,包括托管的和非托管的。 在這個(gè)模式中,void Dispose(bool disposing)函數(shù)通過(guò)一個(gè)disposing參數(shù)來(lái)區(qū)別當(dāng)前是否是被Dispose()調(diào)用。如果是被Dispose()調(diào)用,那么需要同時(shí)釋放托管和非托管的資源。如果是被~Foo()(也就是C#的Finalize())調(diào)用了,那么只需要釋放非托管的資源即可。 這是因?yàn)椋?/span>Dispose()函數(shù)是被其它代碼顯式調(diào)用并要求釋放資源的,而Finalize是被GC調(diào)用的。在GC調(diào)用的時(shí)候Foo所引用的其它托管對(duì)象可能還不需要被銷毀,并且即使要銷毀,也會(huì)由GC來(lái)調(diào)用。因此在Finalize中只需要釋放非托管資源即可。另外一方面,由于在Dispose()中已經(jīng)釋放了托管和非托管的資源,因此在對(duì)象被GC回收時(shí)再次調(diào)用Finalize是沒(méi)有必要的,所以在Dispose()中調(diào)用GC.SuppressFinalize(this)避免重復(fù)調(diào)用Finalize。 然而,即使重復(fù)調(diào)用Finalize和Dispose也是不存在問(wèn)題的,因?yàn)橛凶兞?/span>m_disposed的存在,資源只會(huì)被釋放一次,多余的調(diào)用會(huì)被忽略過(guò)去。 因此,上面的模式保證了: 1、 Finalize只釋放非托管資源; 2、 Dispose釋放托管和非托管資源; 3、 重復(fù)調(diào)用Finalize和Dispose是沒(méi)有問(wèn)題的; 4、 Finalize和Dispose共享相同的資源釋放策略,因此他們之間也是沒(méi)有沖突的。 在C#中,這個(gè)模式需要顯式地實(shí)現(xiàn),其中C#的~Foo()函數(shù)代表了Finalize()。而在C++/CLI中,這個(gè)模式是自動(dòng)實(shí)現(xiàn)的,C++的類析構(gòu)函數(shù)則是不一樣的。 按照C++語(yǔ)義,析構(gòu)函數(shù)在超出作用域,或者delete的時(shí)候被調(diào)用。在Managed C++(即.NET 1.1中的托管C++)中,析構(gòu)函數(shù)相當(dāng)于CLR中的Finalize()方法,在垃圾收集的時(shí)候由GC調(diào)用,因此,調(diào)用的時(shí)機(jī)是不明確的。在.NET 2.0的C++/CLI中,析構(gòu)函數(shù)的語(yǔ)義被修改為等價(jià)與Dispose()方法,這就隱含了兩件事情: 1、 所有的C++/CLI中的CLR類都實(shí)現(xiàn)了接口IDisposable,因此在C#中可以用using關(guān)鍵字來(lái)訪問(wèn)這個(gè)類的實(shí)例。 2、 析構(gòu)函數(shù)不再等價(jià)于Finalize()了。 對(duì)于第一點(diǎn),這是一件好事,我認(rèn)為在語(yǔ)義上Dispose()更加接近于C++析構(gòu)函數(shù)。對(duì)于第二點(diǎn),Microsoft進(jìn)行了一次擴(kuò)展,做法是引入了“!”函數(shù),如下所示: 1 public ref class Foo
2 { 3 public: 4 Foo(); 5 ~Foo(); // destructor 6 !Foo(); // finalizer 7 }; 8
“!”函數(shù)(我實(shí)在不知道應(yīng)該怎么稱呼它)取代原來(lái)Managed C++中的Finalize()被GC調(diào)用。MSDN建議,為了減少代碼的重復(fù),可以寫這樣的代碼: 1 ~Foo()
2 { 3 //釋放托管的資源 4 this->!Foo(); 5 } 6 7 !Foo() 8 { 9 //釋放非托管的資源 10 } 11
對(duì)于上面這個(gè)類,實(shí)際上C++/CLI生成對(duì)應(yīng)的C#代碼是這樣的: 1 public class Foo
2 { 3 private void !Foo() 4 { 5 // 釋放非托管的資源 6 } 7 8 private void ~Foo() 9 { 10 // 釋放托管的資源 11 !Foo(); 12 } 13 14 public Foo() 15 { 16 } 17 18 public void Dispose() 19 { 20 Dispose(true); 21 GC.SuppressFinalize(this); 22 } 23 24 protected virtual void Dispose(bool disposing) 25 { 26 if (disposing) 27 { 28 ~Foo(); 29 } 30 else 31 { 32 try 33 { 34 !Foo(); 35 } 36 finally 37 { 38 base.Finalize(); 39 } 40 } 41 } 42 43 protected void Finalize() 44 { 45 Dispose(false); 46 } 47 } 48
由于~Foo()和!Foo()不會(huì)被重復(fù)調(diào)用(至少MS這樣認(rèn)為),因此在這段代碼中沒(méi)有和前面m_disposed相同的變量,但是基本的結(jié)構(gòu)是一樣的。 并且,可以看到實(shí)際上并不是~Foo()和!Foo()就是Dispose和Finalize,而是C++/CLI編譯器生成了兩個(gè)Dispose和Finalize函數(shù),并在合適的時(shí)候調(diào)用它們。C++/CLI其實(shí)已經(jīng)做了很多工作,但是唯一的一個(gè)問(wèn)題就是依賴于用戶在~Foo()中調(diào)用!Foo()。 關(guān)于資源釋放,最后一點(diǎn)需要提的是Close函數(shù)。在語(yǔ)義上它和Dispose很類似,按照MSDN的說(shuō)法,提供這個(gè)函數(shù)是為了讓用戶感覺(jué)舒服一點(diǎn),因?yàn)閷?duì)于某些對(duì)象,例如文件,用戶更加習(xí)慣調(diào)用Close()。 然而,畢竟這兩個(gè)函數(shù)做的是同一件事情,因此MSDN建議的代碼就是: 1 public void Close()
2 { 3 Dispose((); 4 } 5 6 這里直接調(diào)用不帶參數(shù)的Dispose函數(shù)以獲得和Dispose相同的語(yǔ)義。這樣似乎就圓滿了,但是從另外一方面說(shuō),如果同時(shí)提供了Dispose和Close,會(huì)給用戶帶來(lái)一些困惑。沒(méi)有看到代碼細(xì)節(jié)的前提下,很難知道這兩個(gè)函數(shù)到底有什么區(qū)別。因此在.NET的代碼設(shè)計(jì)規(guī)范中說(shuō),這兩個(gè)函數(shù)實(shí)際上只能讓用戶用一個(gè)。因此建議的模式是: 1 public class Foo: IDisposable
2 { 3 public void Close() 4 { 5 Dispose(); 6 } 7 8 void IDisposable.Dispose() 9 { 10 Dispose(true); 11 GC.SuppressFinalize(this); 12 } 13 14 protected virtual void Dispose(bool disposing) 15 { 16 // 同前 17 } 18 } 19
這里使用了一個(gè)所謂的接口顯式實(shí)現(xiàn):void IDisposable.Dispose()。這個(gè)顯式實(shí)現(xiàn)只能通過(guò)接口來(lái)訪問(wèn),但是不能通過(guò)實(shí)現(xiàn)類來(lái)訪問(wèn)。因此: 1 Foo foo = new Foo();
2 3 foo.Dispose(); // 錯(cuò)誤 4 (foo as IDisposable).Dispose(); // 正確 5
這樣做到了兼顧兩者。對(duì)于喜歡使用Close的人,可以直接用 foo.Close(),并且他看不到 Dispose()。對(duì)于喜歡Dispose的,他可以把類型轉(zhuǎn)換為 IDisposable 來(lái)調(diào)用,或者使用using語(yǔ)句。兩者皆大歡喜! |
|