动态反射——Load,LoadFrom和LoadFile
【問】
假設有一個類庫文件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 APublic Class ClassA
Public Sub New()
Console.WriteLine("成功執行ClassA的構造函數。")
End Sub
End Class
End Namespace
【LibraryB.dll】
Imports LibraryANamespace 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的構造函數?!?。
【總結】
1) AssemblyName和Namespace不是一回事情,AssemblyName是供.NET使用的;而Namespace是客戶自定義的“歸檔”類的命名空間。Assembly.Load需要前者,但是剛創建項目時,VS默認情況下兩者一致。
2)LoadFile只管加載程序集文件,但是反射僅限于當前程序集自身中的方法、屬性等,如果需要反射引用到的外部程序集,必須使用LoadFrom。
3)LoadFrom對于相同程序集文件只加載第一次,因此只返回第一次的結果;要反射不同路徑但是相同的程序集文件,必須使用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表示“里程碑”式的程序更新(比如Windows98到XP,那么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)? AttributeCulture和AttributeVersion不能使用以上方法獲取,只能使用AttributeName方法。因為它們是編譯器生成的,不是直接輸出供外部客戶通過“右鍵”=>“屬性”的方式直接可以看到的。
轉載于:https://www.cnblogs.com/ServiceboyNew/archive/2011/11/17/2241215.html
總結
以上是生活随笔為你收集整理的动态反射——Load,LoadFrom和LoadFile的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux溢出提权
- 下一篇: 各种Exit退出函数用法