動態反射——Load,LoadFrom和LoadFile

来源:http://www.cnblogs.com/shouce/archive/2016/02/17/5194226.html
-Advertisement-
Play Games

【問】 假設有一個類庫文件LibraryA,其中有一個ClassA,該類的AssemblyName為“LibraryA”(編譯後的文件是LibraryA.dll)。另外有一個LibraryB.dll類庫文件,其中AssemblyName和其命名空間一樣,並且其引用LibraryA.dll。它們代碼如


【問】

假設有一個類庫文件LibraryA,其中有一個ClassA,該類的AssemblyName為“LibraryA”(編譯後的文件是LibraryA.dll)。另外有一個LibraryB.dll類庫文件,其中AssemblyName和其命名空間一樣,並且其引用LibraryA.dll。它們代碼如下:

[C#]

【LibraryA.dll】

複製代碼
namespace A
{
public class ClassA
{
public ClassA()
{
Console.WriteLine("成功執行ClassA的構造函數。");
}
}
}
複製代碼

【LibraryB.dll】

複製代碼
using LibraryA;

namespace LibraryB
{
public class ClassB
{
public ClassA GetClassA()
{
return new ClassA();
}

public ClassB()
{
Console.WriteLine("成功執行ClassB的構造函數。");
}
}
}
複製代碼

[VB.NET]

【LibraryA.dll】

複製代碼
Namespace A
Public Class ClassA
Public Sub New()
Console.WriteLine("成功執行ClassA的構造函數。")
End Sub
End Class
End Namespace
複製代碼

【LibraryB.dll】

複製代碼
Imports LibraryA

Namespace LibraryB
Public Class ClassB
Public Function GetClassA() As ClassA
Return New ClassA()
End Function

Public Sub New()
Console.WriteLine("成功執行ClassB的構造函數。")
End Sub
End Class
End Namespace
複製代碼

現在假設有一個控制台程式(包含Main主函數,該控制台類也已經引用了LibraryA)。要求:

       1、設法使用反射方法運行LibraryA的構造函數。

       2、設法使用動態載入方法(只允許載入LibraryB.dll,假設位於D:\目錄下),運行GetClassA方法。

       假設在D:\ 下有兩個文件夾(f1和f2)。在f1中複製原本LibraryB.dll一份;然後在f2複製修改過的LibraryB.dll(就是構造函數輸出變更為“成功執行ClassB的構造函數更新版”,然後重新編譯拷貝進入f2)。我在控制台主程式中動態載入兩個不同路徑,但是相同版本的LibraryB.dll。請問以下程式運行之後結果是什麼?

[C#]

Assembly asm = Assembly.LoadFrom(@“D:\f1\LibraryB.dll”);
asm.CreateInstance(“LibraryB.ClassB”);
asm = Assembly.LoadFrom (@“D:\f2\LibraryB.dll”);
asm.CreateInstance(“LibraryB.ClassB”);

[VB.NET]

Dim asm As Assembly = Assembly.LoadFrom (@“D:\f1\LibraryB.dll”)
asm.CreateInstance(“LibraryB.ClassB”)
asm = Assembly. LoadFrom (@“D:\f2\LibraryB.dll”)
asm.CreateInstance(“LibraryB.ClassB”)

【錯誤回答】

 1)

[C#]

Assembly asm = Assembly.Load(“A”);
asm.CreateInstance(“A.ClassA”);

[VB.NET]

Dim asm As Assembly = Assembly.Load(“A”)
asm.CreateInstance(“A.ClassA”)

理由:Load需要一個AssemblyName,也就是Namespace的名稱。然後後面的CreateInstance需要一個“命名空間名.類名”。

2)

[C#]

Assembly asm = Assembly.LoadFile(@“D:\LibraryB.dll”);
asm.CreateInstance(“LibraryB.ClassB”).GetType().GetMethod(“GetClassA”);

[VB.NET]

Dim asm As Assembly = Assembly.LoadFile(“D:\LibraryB.dll”)
asm.CreateInstance(“LibraryB.ClassB”).GetType().GetMethod(“GetClassA”)

3)輸出兩行話——

成功執行ClassB的構造函數。

成功執行ClassB的構造函數更新版。

【正解】

第一問:初學者似乎沒有區分“AssemblyName”、“Namespace”的區別——的確,很少有教師去教他們區別這兩者之間的差異。AssemblyName是“程式集”名字,是用於記錄程式信息的一種特殊唯一標識符,供.NET的底層CLR調用識別的;Namespace是面向程式設計者,使得客戶可以將不同的類進行歸檔,因此Namespace類似文件夾,而每一個Class類似於文件夾中的文件;至於Assembly相當於“硬碟的捲標”作用。

因此,Assembly.Load的第一個參數需要的是AssemblyName,和Namespace並無直接聯繫。你完全可以根據需要修改AssemblyName(方法:右鍵項目,選擇“屬性”,在“程式(Application)”面板上就可以看到)。

第二問和第三問其實是考察如何正確使用LoadFile和LoadFrom動態載入指定的類庫。

第二問:錯誤代碼將導致一個“無法找到載入文件”的類似錯誤,其原因是在於LibraryB.dll引用了LibraryA.dll,但是如果使用了LoadFile,則動態反射LibraryB.dll的時候不會自動載入程式集LibraryA.dll,但是“GetClassA”方法卻需要使用到LibraryA中的類。繼而引發錯誤。而要使得被引用的其它dll也一併載入進來,應該使用LoadFrom方法。

第三問和第二問相反,考察LoadFile和LoadFrom載入一個程式集相同,但是處於不同位置的類庫(註意:這裡“程式集相同”是指在源代碼基礎上略作修改,形成副本的dll)。

第三問:LoadFile只管載入類庫,只要指定了絕對或者相對屋裡路徑的類庫,其總是載入並以此為準;但是LoadFrom不同——如果遇到了相同的程式集的類庫文件,以第一次的載入為準。所以輸出的兩句話都是“成功執行ClassB的構造函數。”。

【總結】

1AssemblyNameNamespace不是一回事情,AssemblyName是供.NET使用的;而Namespace是客戶自定義的“歸檔”類的命名空間。Assembly.Load需要前者,但是剛創建項目時,VS預設情況下兩者一致。

2LoadFile只管載入程式集文件,但是反射僅限於當前程式集自身中的方法、屬性等,如果需要反射引用到的外部程式集,必須使用LoadFrom

3LoadFrom對於相同程式集文件只載入第一次,因此只返回第一次的結果;要反射不同路徑但是相同的程式集文件,必須使用LoadFile

【拓展】

“程式集”是包含一個或者多個類型定義文件和資源文件的集合,一般地當創建了一個完整的.NET程式(無論是C#或者是VB.NET的),VS預設就為其產生一個程式集。程式集一般包含程式的若幹信息(比如程式的版本號等),它們都是存儲在一個叫做“Assembly.cs”(VB.NET中是AssemblyInfo.vb)的文件中。如果雙擊打開該文件,我們便可窺知一二(註釋刪除)——

[C#]

複製代碼
[assembly: AssemblyTitle("CSharp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft IT")]
[assembly: AssemblyProduct("CSharp")]
[assembly: AssemblyCopyright("Copyright © Microsoft IT 2011")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
複製代碼

[VB.NET]

複製代碼
<Assembly: AssemblyTitle("CSharp")>
<Assembly: AssemblyDescription("")>
<Assembly: AssemblyConfiguration("")>
<Assembly: AssemblyCompany("Microsoft IT")>
<Assembly: AssemblyProduct("CSharp")>
<Assembly: AssemblyCopyright("Copyright © Microsoft IT 2011")>
<Assembly: AssemblyTrademark("")>
<Assembly: AssemblyCulture("")>
<Assembly: AssemblyVersion("1.0.0.0")>
<Assembly: AssemblyFileVersion("1.0.0.0")>
複製代碼

下麵來說說如何讀取這些數據:

1)AssemblyVersion是程式的版本號。一般地,通過讀取程式的版本號可以瞭解程式是否處於最新狀態,不少更新程式往往就通過類似手段或者方式更新程式的。要讀取一個當前程式的版本號,我們通過Assembly.GetName方法獲取Version屬性即可。

[C#]

AssemblyName an = Assembly.GetExecutingAssembly().GetName();
Console.WriteLine(an.Version);

[VB.NET]

Dim an As AssemblyName = Assembly.GetExecutingAssembly().GetName()
Console.WriteLine(an.Version)

GetExecutingAssembly是一個獲取當前正運行的程式的Assembly實體。如果要讀取指定某個程式的版本號,我們可以使用Assembly.LoadFile或者Assembly.LoadFrom載入一個dll或者是exe文件,然後通過GetName獲取AssemblyName,再按照上面相同的方法通過諸如Version一類的屬性就可以了。

順便說一下“AssemblyName”。通過“拓展”之前的分析就可以其是一個“程式集”名稱。實際上.NET把它定義成一個類,專門可以用於獲取Culture(對應AssemblyCulture),Name(就是指當前程式的“程式集名稱”)和Version(指代AssemblyVersion)等信息其中Version還包括“Major”(主版本號)、“Minor”(次版本號)、“Build”(編譯版本號)和“Revision”(修正版本號)。它們作用分別是:

1)  Major(主板本號)+Minor(次版本號):兩個合成用於對外發佈,對外告知客戶目前程式的版本。一般地,Major表示“里程碑”式的程式更新(比如Windows98XP,那麼Major就會產生影響,自增1);而Minor是在當前程式中進行的局部重大更新(比如在原來程式基礎上增刪了功能,或者發現了漏洞進行修補等)時用到。

2)  Build(編譯版本號):內部告知開發人員或者測試人員,目前該程式從開始編譯到全部完成,總共編譯的次數(每重新編譯一次此自增1)。

3)  Revision(修正版本號):每當有一個Bug在內測時被髮現,此版本號在原來基礎上加1,表示總共從開始到完成歷經多少了Bug修複。

AssemblyVersion和AssemblyFileVersion一般需要保持一致。前者是被.NET內部反射使用到的,後者是對外,可以通過右鍵=>屬性中查看得到。比如查看Word2010程式我們可以瞭解以下信息:

                     

第一個“14”表示Word家族已經歷經到目前開發了14個不同的“里程碑”式版本,第二個“0”表示目前為止尚未發現在office2010中有明顯增加或是刪除功能;“5123”表示該版本的Word程式總共編譯了5123次,而“5000”表示從開始到結束總共修正了5000個Bug。

除了可以在Assembly.cs中看到文件信息,我們同樣可以通過右鍵某個csproj(VB.NET中是vbproj),“屬性”=>“應用程式(Application)”中得到相同的信息:

預設主版本號是1,其餘都是0;下方的一個“覆選框”是“自動為Revision版本號自增1”;也就是說,每次對外發佈時,該程式的Revision會在原來基礎上加上1。

2)除了版本號之外,其餘的Assembly信息(比如AssemblyTitle)可以通過這樣的方式獲得:

[C#]

AssemblyTitleAttribute at = (AssemblyTitleAttribute) AssemblyTitle.GetCultureAttribute(
Assembly.GetExecutingAssembly(),typeof(AssemblyTitle)
);
Console.WriteLine(at.Title);

[VB.NET]

   Dim at As AssemblyTitleAttribute = DirectCast(
AssemblyTitleAttribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), GetType(AssemblyTitleAttribute)
), AssemblyTitleAttribute) Console.WriteLine(at.Title)


更一般地,因為Assembly文件中這些都是“特性”類(省略了尾碼Attribute)。因此我們可以使用反射的方法獲取它們,偽代碼描述如下:

[C#]

XXXAttribute 變數名= XXXAttribute.GetCustomAttribute(Assembly實體對象,typeof(XXXAttribute));

變數名.屬性;

[VB.NET]

Dim 變數名 As XXXAttribute== XXXAttribute.GetCustomAttribute(Assembly實體對象,typeof(XXXAttribute))

變數名.屬性

幾點說明:

1)  XXX”表示對應Assembly文件中的“assembly:”後面的那個部分。

2)  AttributeCultureAttributeVersion不能使用以上方法獲取,只能使用AttributeName方法。因為它們是編譯器生成的,不是直接輸出供外部客戶通過“右鍵”=>“屬性”的方式直接可以看到的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [ASP.NET MVC] ASP.NET Identity登入技術應用 情景 ASP.NET Identity是微軟所貢獻的開源項目,用來提供ASP.NET的驗證、授權等等機制。在ASP.NET Identity里除了提供最基礎的:用戶註冊、密碼重設、密碼驗證等等基礎功能之外,也提供了進階的:Co
  • /// <summary> /// 獲得四位的隨機數 /// </summary> /// <returns></returns> public string Randomnum() { Random ro = new Random(); int iResult; int iUp = 999; in
  • 應用開發中,開發者時常需要獲取一些系統、用戶信息用於數據統計遙測、問題反饋、用戶識別等功能。本文旨在介紹在 Windows UWP 應用中獲取一些常用系統、用戶信息的方法。示例項目代碼可參見 Github: https://github.com/validvoid/UWP-SystemInfoCol
  • 運行Windows Task Manager,查看Users標簽,可以看到所有登錄電腦的用戶,現在Insus.NET想命名用VB.NET的WinForm程式去獲取當前的用戶名. 獲取程式簡單,可以參考之: 源代碼: Declare Function GetUserName Lib "advapi32
  • 如何查看查詢字元串?→輸入包含查詢字元串的URL,比如:http://localhost:54176/api/ProductCategories?name=darren&age=25→點擊Params,查詢字元串以鍵值隊集合的形式存儲→選中鍵值對,右鍵選擇"EncodeURIComponent"可以
  • 一個程式也許會被多個用戶運行,如下:那在VB.NET的WinForm環境下,怎樣獲取User Name呢?可從下麵的方法: 代碼: Public Shared Function GetProcessOwner(ByVal ProcessName As String) As String Dim po
  • 描述線程與進程的區別? 什麼是Windows服務,它的生命周期與標準的EXE程式有什麼不同 Windows上的單個進程所能訪問的最大記憶體量是多少?它與系統的最大虛擬記憶體一樣嗎?這對於系統設計有什麼影響? EXE和DLL之間的區別是什麼? 什麼是強類型,什麼是弱類型?哪種更好些?為什麼? PID是什麼
  • 出處:http://www.cnblogs.com/_popc 引言:當前能看到很多互聯網網站中有關web前端優化都採用js合併壓縮的方式輸出。樓主找了幾個地址參考 地址1、地址2 那麼下麵就開始瞭如何實現。 1.既然需要將js合併壓縮輸出那麼就先需要構造一個如上鏈接所示的那樣的鏈接地址。 @usi
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...