动态反射——Load,LoadFrom和LoadFile
【問】
假設(shè)有一個類庫文件LibraryA,其中有一個ClassA,該類的AssemblyName為“LibraryA”(編譯后的文件是LibraryA.dll)。另外有一個LibraryB.dll類庫文件,其中AssemblyName和其命名空間一樣,并且其引用LibraryA.dll。它們代碼如下:
[C#]
【LibraryA.dll】
namespace A{
public class ClassA
{
public ClassA()
{
Console.WriteLine("成功執(zhí)行ClassA的構(gòu)造函數(shù)。");
}
}
}
【LibraryB.dll】
using LibraryA;namespace LibraryB
{
public class ClassB
{
public ClassA GetClassA()
{
return new ClassA();
}
public ClassB()
{
Console.WriteLine("成功執(zhí)行ClassB的構(gòu)造函數(shù)。");
}
}
}
[VB.NET]
【LibraryA.dll】
Namespace APublic Class ClassA
Public Sub New()
Console.WriteLine("成功執(zhí)行ClassA的構(gòu)造函數(shù)。")
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("成功執(zhí)行ClassB的構(gòu)造函數(shù)。")
End Sub
End Class
End Namespace
現(xiàn)在假設(shè)有一個控制臺程序(包含Main主函數(shù),該控制臺類也已經(jīng)引用了LibraryA)。要求:
?????? 1、設(shè)法使用反射方法運行LibraryA的構(gòu)造函數(shù)。
?????? 2、設(shè)法使用動態(tài)加載方法(只允許加載LibraryB.dll,假設(shè)位于D:\目錄下),運行GetClassA方法。
?????? 假設(shè)在D:\ 下有兩個文件夾(f1和f2)。在f1中復制原本LibraryB.dll一份;然后在f2復制修改過的LibraryB.dll(就是構(gòu)造函數(shù)輸出變更為“成功執(zhí)行ClassB的構(gòu)造函數(shù)更新版”,然后重新編譯拷貝進入f2)。我在控制臺主程序中動態(tài)加載兩個不同路徑,但是相同版本的LibraryB.dll。請問以下程序運行之后結(jié)果是什么?
[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)輸出兩行話——
成功執(zhí)行ClassB的構(gòu)造函數(shù)。
成功執(zhí)行ClassB的構(gòu)造函數(shù)更新版。
【正解】
第一問:初學者似乎沒有區(qū)分“AssemblyName”、“Namespace”的區(qū)別——的確,很少有教師去教他們區(qū)別這兩者之間的差異。AssemblyName是“程序集”名字,是用于記錄程序信息的一種特殊唯一標識符,供.NET的底層CLR調(diào)用識別的;Namespace是面向程序設(shè)計者,使得客戶可以將不同的類進行歸檔,因此Namespace類似文件夾,而每一個Class類似于文件夾中的文件;至于Assembly相當于“硬盤的卷標”作用。
因此,Assembly.Load的第一個參數(shù)需要的是AssemblyName,和Namespace并無直接聯(lián)系。你完全可以根據(jù)需要修改AssemblyName(方法:右鍵項目,選擇“屬性”,在“程序(Application)”面板上就可以看到)。
第二問和第三問其實是考察如何正確使用LoadFile和LoadFrom動態(tài)加載指定的類庫。
第二問:錯誤代碼將導致一個“無法找到加載文件”的類似錯誤,其原因是在于LibraryB.dll引用了LibraryA.dll,但是如果使用了LoadFile,則動態(tài)反射LibraryB.dll的時候不會自動加載程序集LibraryA.dll,但是“GetClassA”方法卻需要使用到LibraryA中的類。繼而引發(fā)錯誤。而要使得被引用的其它dll也一并加載進來,應該使用LoadFrom方法。
第三問和第二問相反,考察LoadFile和LoadFrom加載一個程序集相同,但是處于不同位置的類庫(注意:這里“程序集相同”是指在源代碼基礎(chǔ)上略作修改,形成副本的dll)。
第三問:LoadFile只管加載類庫,只要指定了絕對或者相對屋里路徑的類庫,其總是加載并以此為準;但是LoadFrom不同——如果遇到了相同的程序集的類庫文件,以第一次的加載為準。所以輸出的兩句話都是“成功執(zhí)行ClassB的構(gòu)造函數(shù)。”。
【總結(jié)】
1) AssemblyName和Namespace不是一回事情,AssemblyName是供.NET使用的;而Namespace是客戶自定義的“歸檔”類的命名空間。Assembly.Load需要前者,但是剛創(chuàng)建項目時,VS默認情況下兩者一致。
2)LoadFile只管加載程序集文件,但是反射僅限于當前程序集自身中的方法、屬性等,如果需要反射引用到的外部程序集,必須使用LoadFrom。
3)LoadFrom對于相同程序集文件只加載第一次,因此只返回第一次的結(jié)果;要反射不同路徑但是相同的程序集文件,必須使用LoadFile。
【拓展】
“程序集”是包含一個或者多個類型定義文件和資源文件的集合,一般地當創(chuàng)建了一個完整的.NET程序(無論是C#或者是VB.NET的),VS默認就為其產(chǎn)生一個程序集。程序集一般包含程序的若干信息(比如程序的版本號等),它們都是存儲在一個叫做“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")>
下面來說說如何讀取這些數(shù)據(jù):
1)AssemblyVersion是程序的版本號。一般地,通過讀取程序的版本號可以了解程序是否處于最新狀態(tài),不少更新程序往往就通過類似手段或者方式更新程序的。要讀取一個當前程序的版本號,我們通過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(次版本號):兩個合成用于對外發(fā)布,對外告知客戶目前程序的版本。一般地,Major表示“里程碑”式的程序更新(比如Windows98到XP,那么Major就會產(chǎn)生影響,自增1);而Minor是在當前程序中進行的局部重大更新(比如在原來程序基礎(chǔ)上增刪了功能,或者發(fā)現(xiàn)了漏洞進行修補等)時用到。
2)? Build(編譯版本號):內(nèi)部告知開發(fā)人員或者測試人員,目前該程序從開始編譯到全部完成,總共編譯的次數(shù)(每重新編譯一次此自增1)。
3)??Revision(修正版本號):每當有一個Bug在內(nèi)測時被發(fā)現(xiàn),此版本號在原來基礎(chǔ)上加1,表示總共從開始到完成歷經(jīng)多少了Bug修復。
AssemblyVersion和AssemblyFileVersion一般需要保持一致。前者是被.NET內(nèi)部反射使用到的,后者是對外,可以通過右鍵=>屬性中查看得到。比如查看Word2010程序我們可以了解以下信息:
?????????????????????
第一個“14”表示W(wǎng)ord家族已經(jīng)歷經(jīng)到目前開發(fā)了14個不同的“里程碑”式版本,第二個“0”表示目前為止尚未發(fā)現(xiàn)在office2010中有明顯增加或是刪除功能;“5123”表示該版本的Word程序總共編譯了5123次,而“5000”表示從開始到結(jié)束總共修正了5000個Bug。
除了可以在Assembly.cs中看到文件信息,我們同樣可以通過右鍵某個csproj(VB.NET中是vbproj),“屬性”=>“應用程序(Application)”中得到相同的信息:
默認主版本號是1,其余都是0;下方的一個“復選框”是“自動為Revision版本號自增1”;也就是說,每次對外發(fā)布時,該程序的Revision會在原來基礎(chǔ)上加上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方法。因為它們是編譯器生成的,不是直接輸出供外部客戶通過“右鍵”=>“屬性”的方式直接可以看到的。
轉(zhuǎn)載于:https://www.cnblogs.com/ServiceboyNew/archive/2011/11/17/2241215.html
總結(jié)
以上是生活随笔為你收集整理的动态反射——Load,LoadFrom和LoadFile的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux溢出提权
- 下一篇: 各种Exit退出函数用法