一些關於C#反射的知識,估計也就最多達到使用API的程度,至於要深入瞭解,以現在的水平估計很難做到,所以下面此篇文章,以作為一個階段的總結。 對於反射的總結,我想從以下幾個方面展開,首先是反射程式集,模組,類的成員以及成員的一些資訊;接下來就是動態調用類的成員方法;第三個方面就動態產生程式集,模組和類以及類的成員。好了,現在就讓我們從反射各種資訊開始吧 在C#中,我們要使用反射,首先要搞清楚以下命名空間中幾個類的關係: System.Reflection命名空間 (1) AppDomain:應用程式定義域,可以將其理解為一組程式集的邏輯容器 (2) Assembly:程式集類 (3) Module:模組類 (4) Type:使用反射得到類型資訊的最核心的類 他們之間是一種從屬關係,也就是說,一個AppDomain可以包含N個Assembly,一個Assembly可以包含N個Module,而一個Module可以包含N個Type. AppDomain這個類我們等下再來講解。我們先關注Assembly個類 在程式中,如果我們要動態載入一個程式集怎麼辦呢?有幾種方式可以使用,分別是Load、LoadFrom和LoadWithPartialName三個Assembly的靜態方法. 先來講解Assembly.Load方法,該方法會有多個重載版本,其中一個就是提供者集的詳細資料,即程式集的標識,包括程式集的名稱,版本,地區資訊,公有密鑰標記,全部都是以一個字串的形式提供,例如:"MyAssembly,Version=1.0.0.0,culture=zh-CN,PublicKeyToken=47887f89771bc57f”. 那麼,使用Assembly.Load載入程式集的順序是怎樣的呢?首先它會去全域組件快取尋找,然後到應用程式的根目錄尋找,最後會到應用程式的私人路徑尋找。 當然,如果你使用的是弱命名程式集,也即只給出程式集的名稱,那麼這個時候,CLR將不會在程式集上應用任何安全或者部署策略,而且Load也不會到全域緩衝程式集中尋找程式集。 測試載入弱命名程式集的例子如下: (1) 建立一個控制台應用程式的工程,同時勾選建立解決方案 (2) 在解決方案中建立一個類庫的項目,隨便寫一個類和一個方法 (3) 在控制台項目中,首先不添加引用,直接在Main方法中添加如下代碼: Assembly assembly = Assembly.Load("MyAssembly"); if (assembly != null) { Console.WriteLine("載入成功"); } 執行程式,會拋出異常,說找不到該程式集。什麼原因呢?因為我們使用的是弱命名程式集,Load方法不會去全域組件快取中尋找,而該應用程式目錄下又沒有該程式集,所以程式找不到。這個時候,我們把程式稍微改一下,不用添加代碼,只需添加對MyAssembly的引用,重新運行程式,載入成功了。 接下來,我們就要看看Load怎麼載入強命名程式集了,這個步驟稍微有些複雜。還是剛才的項目,找到MyAssembly.dll程式集所在的目錄,一般在bin"Debug目錄下 (1)產生金鑰組檔案 sn –k MyAssemblyKey.keys 你也可以自己隨便起一個金鑰組檔案名稱 (2)產生公開金鑰檔案 sn –p MyAssemblyKey.keys MyAssemblyPublicKey.PublicKey 註:查看公開金鑰命令:sn –tp MyAssemblyPublicKey.PublicKey (3)建立強命名程式集。 很簡單,只需要在聲明命名空間的那句代碼上加上如下特性: [assembly:AssemblyKeyFileAttribute(@”D:"Test"MyAssemblyKey.keys”)] (4) 編譯項目 (5) 將程式集添加到程式集全域緩衝 gacutil –i MyAssembly.dll 這個時候,轉到載入程式集的項目中,將Load方法中的參數改為”程式集名,Version=版本,culture=地區資訊,PublicKeyToken=公開金鑰“,然後再去掉對程式集的引用,我們會發現,程式運行成功。表明Load到全域緩衝區尋找到了該程式集。 使用Load方法載入程式集,特別是強命名程式集,能在程式集上應用安全和部署策略,推薦使用該方法動態載入程式集,至於LoadFrom和LoadWithPartialName。 首先我們還是來看看LoadFrom方法,這個方法的原理是這樣的:我們如果要使用它來動態載入程式集,必須告訴它程式集的路徑,也即在哪個目錄下面,CLR會去載入與你指定的路徑完全符合的程式集。記住,當我們指定程式集路徑時,不能包括任何關於程式集強命名的資訊,所以,CLR不會在我們指定的組件檔上應用任何策略,而且也不會去任何其他的地方搜尋程式集,簡言之,它就是指哪打哪,呵呵。 例如:你有個程式集在D:/Test/MyAssembly.dll,你要用Assembly.LoadFrom載入該程式集,代碼就如下: Assembly assembly = Assembly.LoadFrom(@”D:/Test/MyAssembly.dll”); 對於,LoadWithParitalName方法,推薦大家最好不要使用它,因為程式無法確定最終要去載入哪個程式集的版本,所以我們這裡只是簡單的介紹一下它的工作原理:你可以傳遞一個程式集標識給它,包括程式集名稱,至於其他資訊是可選的(地區資訊,公有密鑰等),該方法執行時,會首先檢查應用程式中設定檔的qualifyAssembly節點,如果存在,則把該部分名稱的程式集替換成完全的程式集標識,如果不存在,則使用程式集名稱先到應用程式根目錄下尋找,然後是私人目錄,沒有找到的話,就到程式集全域緩衝中尋找。簡單過程如下: 應用程式根目錄 -> 應用程式私人目錄 -> 程式集全域緩衝. Assembly.Load()方法,Assembly.LoadFrom()方法,Assembly.LoadFile()方法的區別! 1,Assembly.Load() 這個方法通過程式集的長名稱(包括程式集名,版本資訊,語言文化,公開金鑰標記)來載入程式集的,會載入此程式集引用的其他程式集,一般情況下都應該優先使用 這個方法,他的執行效率比LoadFrom要高很多,而且不會造成重複載入的問題(原因在第2點上說明) 使用這個方法的時候, CLR會應用一定的策略來尋找程式集,實際上CLR按如下的順序來定位程式集: ⑴如果程式集有強式名稱,在首先在全域程式集緩(GAC)中尋找程式集。 ⑵如果程式集的強式名稱沒有正確指定或GAC中找不到,那麼通過設定檔中的<codebase>元素指定的URL來尋找 ⑶如果沒有指定強式名稱或是在GAC中找不到,CLR會探測特定的檔案夾: 假設你的應用程式目錄是C:/AppDir,<probing>元素中的privatePath指定了一個路徑Path1,你要定位的程式集是AssemblyName.dll則CLR將按照如下順序定位程式集 C:/AppDir/AssemblyName.dll C:/AppDir/AssemblyName/AssemblyName.dll C:/AppDir/Path1/AssemblyName.dll C:/AppDir/Path1/AssemblyName/AssemblyName.dll 如果以上方法不能找到程式集,會發生編譯錯誤,如果是動態載入程式集,會在運行時拋出異常! 2,Assembly.LoadFrom() 這個方法從指定的路徑來載入程式集,實際上這個方法被調用的時候,CLR會開啟這個檔案,擷取其中的程式集版本,語言文化,公開金鑰標記等資訊,把他們傳遞給 Load方法,接著,Load方法採用上面的策略來尋找程式集。如果找到了程式集,會和LoadFrom方法中指定的路徑做比較,如果路徑相同,該程式集 會被認為是應用程式的一部分,如果路徑不同或Load方法沒有找到程式集,那該程式集只是被作為一個“資料檔案”來載入,不會被認為是應用程式的一部分。 這就是在第1點中提到的Load方法比LoadFrom方法的執行效率高的原因。另外,由於可能把程式集作為“資料檔案”來載入,所以使用 LoadFrom從不同路徑載入相同程式集的時候會導致重複載入。當然這個方法會載入此程式集引用的其他程式集。 3,Assembly.LoadFile() 這個方法是從指定的檔案來載入程式集,和上面方法的不同之處是這個方法不會載入此程式集引用的其他程式集! 結論:一般大家應該優先選擇Load方法來載入程式集,如果遇到需要使用LoadFrom方法的時候,最好改變設計而用Load方法來代替! 另:Assembly.LoadFile 與 Assembly.LoadFrom的區別 1、Assembly.LoadFile只載入相應的dll檔案,比如Assembly.LoadFile("abc.dll"),則載入abc.dll,假如abc.dll中引用了def.dll的話,def.dll並不會被載入。 Assembly.LoadFrom則不一樣,它會載入dll檔案及其引用的其他dll,比如上面的例子,def.dll也會被載入。 2、用Assembly.LoadFrom載入一個Assembly時,會先檢查前面是否已經載入過相同名字的Assembly,比如abc.dll有兩個版本(版本1在目錄1下,版本2放在目錄2下),程式一開始時載入了版本1,當使用Assembly.LoadFrom("2//abc.dll")載入版本2時,不能載入,而是返回版本1。Assembly.LoadFile的話則不會做這樣的檢查,比如上面的例子換成Assembly.LoadFile的話,則能正確載入版本2。 LoadFile:載入指定路徑上的組件檔的內容。LoadFrom: 根據程式集的檔案名稱載入組件檔的內容。 區別: LoadFile 方法用來來載入和檢查具有相同標識但位於不同路徑中的程式集.但不會載入程式的依賴項。 LoadFrom 不能用於載入標識相同但路徑不同的程式集。 |