問題: 在我的類中何時需要實現一個完成器?我是否一定要實現完成器
或者只是在我控制著 非托管資源時才需要實現它?我是否一定要在我的完成器中實現 IDisposable 接口?反之又是如何的呢?
答案: 完成器只是在你控制了需要被清除的資源的才需要實現
舉個例子
FileStream 控制了一個本地的文件句柄並且實現了一個完成器
這樣也就保證了 FileStream 被垃圾回收器回收時能釋放這個句柄
不幸的是
完成器給垃圾回收帶來了一定意義上的負擔
因此應僅在必要時才使用完成器
IDisposable 接口的實現表明你的類控制了需要被釋放的資源
並且允許你的類用戶決定是否要釋放它們
因此
任何一個類實現了完成器就一定實現了 IDisposable 接口(如果垃圾回收器能自動釋放資源
那麼也應該允許開發者顯式地調用某個方法來完成同樣的工作)
但這並不是絕對的
並不是所有實現了IDisposable接口的類都要實現一個完成器
設想一下
我的托管類有一個FileStream類型的私有成員
FileStream 控制了一個非托管的資源並且實現了 IDisposable 接口和完成器
當對該實例不再有引用時
FileStream就變成無法訪問的與可終結的了
對我的類而言
它沒有理由在注冊對象隊列裡等待終結
因為內嵌的FileStream的實例已經被注冊了
另一方面
考慮到我的類應該為用戶提供方法以立即釋放它所控制的資源
無論這種方法是直接地還是間接的
因此我的類應當實現IDisposable接口
我的Dispose()實現很簡單
它只是簡單地調用了FileStream的Dispose()方法
切記
盡管如此
你也需要特別小心地釋放共享資源(比如正被別的實例使用的資源)
如果你編寫一個類從外部釋放資源而使該資源可用
請確保你的文檔在這個主題上是清晰明確的
這樣其他人就知道是否正在移交他們給你的那些資源的控制權了
如果你的類不需要實現完成器
在你的Dispose()方法中應當調用GC
SuppressFinalize()方法
以確保系統不會去調用你的實例的完成器
這樣你的實例同時也會從待終結對象集合中被刪除
從而減輕了垃圾回收器在回收過程中的負擔
貫穿於Microsoft
NET Framework中常見的實現模式是給Dispose()方法添加一個Boolean(邏輯)類型的參數
這個Boolean類型的參數指示這個類是否因IDisposable
Dispose()方法被調用或者完成器在運行而正在被釋放(完成器與IDisposable
Dispose()都是委托到該方法上的)
如果確定它要被釋放
GC
SuppressFinalize()就要被調用
如果是通過完成器被釋放
就要避免再使用你的類中實現完成器的 托管成員
因為它們可能已經被終結了
Figure
提供了一些指導性的說明
幫助你在合適的時候在你的類中實現這些結構
問題: 我希望把一些關系到應用程序性能的操作建立在計算機可用內存的基礎上
如何才能最簡單地從操作系統獲取這些信息?
答案: 盡管我知道獲取這類信息其他的一些方法
但當我發現WMI (Windows Management Instrumentation
Windows管理規范)時
我發現它才是完成這類工作最佳的方式
Win
_OperatingSystem類提供了關於操作系統事件的豐富信息
System
Management命名空間則提供了大量的類來訪問WMI的數據
你可以使用 Figure
中的ManagementObjectSearcher類來查詢Win
_OperatingSystem
TotalVisibleMemorySize的值
因為 ManagementObjectCollection (由ManagementObjectSearcher返回)沒有公開訪問集合內部元素的方法
因此我使用了一個foreach循環來枚舉出其中的每一個成員
而且因為我只關心其中的一個值
所以我在第一次枚舉完成後就停止了循環
注意TotalVisibleMemorySize返回的值可能並不是當前的物理內存總量
而是向操作系統報告可利用的內存量
你可以從Win
_OperatingSystem(Win
_OperatingSystem)這個WMI類中學會更多有用的東西
問題: 我嘗試著在未將程序集裝載入我的AppDomain的情況下
獲取該程序集的完全限定名
這可能做到嗎?
答案: 絕對能!System
Reflection
AssemblyName類有一個static類型的方法GetAssemblyName()
這可以返回磁盤上一個程序集的名稱AssemblyName
這個方法只是簡單地打開這個程序集文件
而不會將它裝載入AppDomain
下面的這段代碼
就能在控制台上輸出從命令行傳入的路徑參數對應程序集的完全限定名
static void Main(string [] args)
{
if (argsLength > )
{
try
{
AssemblyName a = AssemblyNameGetAssemblyName(args[]);
ConsoleWriteLine(aFullname);
}
catch(Exception exc)
{
ConsoleWriteLine(excMessage);
}
}
}
注意
同樣的技巧也可以用到本地托管的DLL或者EXE上
你可以在這些文件上挨個地試一試
如果沒有異常被拋出
並且返回了一個有效的名字
那麼這個文件就是托管的
當然
這種方式在所有的本地文件都觸發異常的情況下也存在一些性能上的缺陷
當然還有另一種方法
它不依賴於反射
也不需要裝載Portable Executable (PE)
而是通過分析DLL或者EXE的PE頭中某個標識位是否被置位
由此確定它是否是 托管的
Managed Extensions for C++ requently Asked Questions中有實現這種方法的C++代碼
等價的C#代碼請參見 Figure
問題: 在我的C#應用程序裡有一大堆的foreach循環
當我檢查編譯器生成的MSIL 文件時(Microsoft intermediate language
Microsoft中間語言)時
我發現其中有的循環被嵌入了一個try/finally塊
但我的源代碼裡並沒有使用啊?它們為什麼會在這裡出現?
答案: 問得好!記住foreach循環是用來枚舉那些集合型的數據的
它是通過獲取一個枚舉器後
使用枚舉器的MoveNext操作來實現遍歷
並在Current數據屬性中返回集合中的當前項
這個枚舉器本身則是通過調用該集合對象的GetEnumerator()方法獲得的
因為枚舉器可能實現了IDisposable接口
所以C#的編譯器需要在枚舉完成後將其釋放
為此
編譯器會將這個循環放入一個try塊
然後試著在finally塊中釋放這個枚舉器
如果利用GetEnumerator ()方法返回的枚舉器實現了IDisposable接口
編譯器就會生成一個類似下面這樣的finally塊
((IDisposable)enumerator)Dispose();
如果枚舉器沒有實現IDisposable接口
那麼編譯器仍然不得不試著釋放這個對象
因為不能確定枚舉器的派生類型(從這個函數返回的)沒有實現IDisposable接口
於是
編譯器又會生成類似這樣的一個finally塊
IDisposable disposable = enumerator as IDisposable;
if (disposable != null) disposableDispose();
因為大多數的可枚舉類都有一個GetEnumerator()方法以返回IEnumerator接口(沒有實現IDisposable接口)所以上述的代碼只是編譯器生成結果的一般情形在少數情況下當返回的類型沒有實現IDisposable接口而且是密封的(指不能從其再派生新的類)時編譯器就不需要實現一個try/finally塊了——因為沒有什麼需要釋放的了
問題: 我希望能枚舉出一個對象層中的所有對象但是我不知道如何才能避免很有可能產生的循環引用NET Framework對此提供了什麼幫助沒有?
答案: 遍歷一個對象層中的所有對象並以某種形式將它們序列化非常類似於完成一個BinaryFormatter那樣的遠程格式化程序對此NET Framework也提供了一些很有用的類來完成這類工作SystemRuntimeSerialization命名空間中的ObjectIDGenerator類可以看作是一張專門化的表用於跟蹤對象並賦與每個對象一個唯一的標識符你可以通過這個類查詢對象的ID——它既可以向你提供繼存對象的ID也可以為一個未出現的對象生成一個新的ID正因它能防止你兩次遍歷同一個對象所以你可以很容易地避免循環引用了
From:http://tw.wingwit.com/Article/program/net/201311/12351.html