1. 程式人生 > >(轉)c#反射

(轉)c#反射

cli www 枚舉類 但是 取反 參考 rac 了解 pat

1、 什麽是反射
2、 命名空間與裝配件的關系
3、 運行期得到類型信息有什麽用
4、 如何使用反射獲取類型
5、 如何根據類型來動態創建對象
6、 如何獲取方法以及動態調用方法
7、 動態創建委托

1、什麽是反射
Reflection,中文翻譯為反射。
這是.Net中獲取運行時類型信息的方式,.Net的應用程序由幾個部分:‘程序集(Assembly)’、‘模塊(Module)’、‘類型(class)’組成,而反射提供一種編程的方式,讓程序員可以在程序運行期獲得這幾個組成部分的相關信息,例如:

Assembly類可以獲得正在運行的裝配件信息,也可以動態的加載裝配件,以及在裝配件中查找類型信息,並創建該類型的實例。
Type類可以獲得對象的類型信息,此信息包含對象的所有要素:方法、構造器、屬性等等,通過Type類可以得到這些要素的信息,並且調用之。
MethodInfo包含方法的信息,通過這個類可以得到方法的名稱、參數、返回值等,並且可以調用之。
諸如此類,還有FieldInfo、EventInfo等等,這些類都包含在System.Reflection命名空間下。

2、命名空間與裝配件的關系


很多人對這個概念可能還是很不清晰,對於合格的.Net程序員,有必要對這點進行澄清。
命名空間類似與Java的包,但又不完全等同,因為Java的包必須按照目錄結構來放置,命名空間則不需要。

裝配件是.Net應用程序執行的最小單位,編譯出來的.dll、.exe都是裝配件。

裝配件和命名空間的關系不是一一對應,也不互相包含,一個裝配件裏面可以有多個命名空間,一個命名空間也可以在多個裝配件中存在,這樣說可能有點模糊,舉個例子:
裝配件A:

  1. namespace N1
  2. {
  3. public class AC1 {…}
  4. public class AC2 {…}
  5. }
  6. namespace N2
  7. {
  8. public class AC3 {…}
  9. public class AC4{…}
  10. }
復制代碼

裝配件B:

  1. namespace N1
  2. {
  3. public class BC1 {…}
  4. public class BC2 {…}
  5. }
  6. namespace N2
  7. {
  8. public class BC3 {…}
  9. public class BC4{…}
  10. }
復制代碼

  這兩個裝配件中都有N1和N2兩個命名空間,而且各聲明了兩個類,這樣是完全可以的,然後我們在一個應用程序中引用裝配件A,那麽在這個應用程序中,我們能看到N1下面的類為AC1和AC2,N2下面的類為AC3和AC4。
接著我們去掉對A的引用,加上對B的引用,那麽我們在這個應用程序下能看到的N1下面的類變成了BC1和BC2,N2下面也一樣。
如果我們同時引用這兩個裝配件,那麽N1下面我們就能看到四個類:AC1、AC2、BC1和BC2。

到這裏,我們可以清楚一個概念了,命名空間只是說明一個類型是那個族的,比如有人是漢族、有人是回族;而裝配件表明一個類型住在哪裏,比如有人住在北京、有人住在上海;那麽北京有漢族人,也有回族人,上海有漢族人,也有回族人,這是不矛盾的。

上面我們說了,裝配件是一個類型居住的地方,那麽在一個程序中要使用一個類,就必須告訴編譯器這個類住在哪兒,編譯器才能找到它,也就是說必須引用該裝配件。
那麽如果在編寫程序的時候,也許不確定這個類在哪裏,僅僅只是知道它的名稱,就不能使用了嗎?答案是可以,這就是反射了,就是在程序運行的時候提供該類型的地址,而去找到它。
有興趣的話,接著往下看吧。

3、運行期得到類型信息有什麽用


有人也許疑問,既然在開發時就能夠寫好代碼,幹嘛還放到運行期去做,不光繁瑣,而且效率也受影響。
這就是個見仁見智的問題了,就跟早綁定和晚綁定一樣,應用到不同的場合。有的人反對晚綁定,理由是損耗效率,但是很多人在享受虛函數帶來的好處的時侯還沒有意識到他已經用上了晚綁定。這個問題說開去,不是三言兩語能講清楚的,所以就點到為止了。
我的看法是,晚綁定能夠帶來很多設計上的便利,合適的使用能夠大大提高程序的復用性和靈活性,但是任何東西都有兩面性,使用的時侯,需要再三衡量。
  接著說,運行期得到類型信息到底有什麽用呢?
  還是舉個例子來說明,很多軟件開發者喜歡在自己的軟件中留下一些接口,其他人可以編寫一些插件來擴充軟件的功能,比如我有一個媒體播放器,我希望以後可以很方便的擴展識別的格式,那麽我聲明一個接口:

  1. public interface IMediaFormat
  2. {
  3. string Extension {get;}
  4. Decoder GetDecoder();
  5. }
復制代碼

  這個接口中包含一個Extension屬性,這個屬性返回支持的擴展名,另一個方法返回一個解碼器的對象(這裏我假設了一個Decoder的類,這個類提供把文件流解碼的功能,擴展插件可以派生之),通過解碼器對象我就可以解釋文件流。
  那麽我規定所有的解碼插件都必須派生一個解碼器,並且實現這個接口,在GetDecoder方法中返回解碼器對象,並且將其類型的名稱配置到我的配置文件裏面。
這樣的話,我就不需要在開發播放器的時侯知道將來擴展的格式的類型,只需要從配置文件中獲取現在所有解碼器的類型名稱,而動態的創建媒體格式的對象,將其轉換為IMediaFormat接口來使用。
這就是一個反射的典型應用。

4、如何使用反射獲取類型
首先我們來看如何獲得類型信息。
獲得類型信息有兩種方法,一種是得到實例對象
這個時侯我僅僅是得到這個實例對象,得到的方式也許是一個object的引用,也許是一個接口的引用,但是我並不知道它的確切類型,我需要了解,那麽就可以通過調用System.Object上聲明的方法GetType來獲取實例對象的類型對象,比如在某個方法內,我需要判斷傳遞進來的參數是否實現了某個接口,如果實現了,則調用該接口的一個方法:

  1. public void Process( object processObj )
  2. {
  3. Type t = processsObj.GetType();
  4. if( t.GetInterface(“ITest”) !=null )
  5. }
復制代碼

  另外一種獲取類型的方法是通過Type.GetType以及Assembly.GetType方法,如:
Type t = Type.GetType(“System.String”);
需要註意的是,前面我們講到了命名空間和裝配件的關系,要查找一個類,必須指定它所在的裝配件,或者在已經獲得的Assembly實例上面調用GetType。
本裝配件中類型可以只寫類型名稱,另一個例外是mscorlib.dll,這個裝配件中聲明的類型也可以省略裝配件名稱(.Net裝配件編譯的時候,默認都引用了mscorlib.dll,除非在編譯的時候明確指定不引用它),比如:
System.String是在mscorlib.dll中聲明的,上面的Type t = Type.GetType(“System.String”)是正確的
System.Data.DataTable是在System.Data.dll中聲明的,那麽:
  Type.GetType(“System.Data.DataTable”)就只能得到空引用。
必須:
  Type t = Type.GetType("System.Data.DataTable,System.Data,Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
這樣才可以,大家可以看下面這個帖子:
http://expert.csdn.net/Expert/to ... 2.xml?temp=.1919977
qqchen的回答很精彩

5、如何根據類型來動態創建對象
System.Activator提供了方法來根據類型動態創建對象,比如創建一個DataTable:

  1. Type t = Type.GetType("System.Data.DataTable,System.Data,Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
  2. DataTable table = (DataTable)Activator.CreateInstance(t);
復制代碼

例二:根據有參數的構造器創建對象

  1. namespace TestSpace
  2. {
  3. public class TestClass
  4. {
  5. private string _value;
  6. public TestClass(string value)
  7. {
  8. _value=value;
  9. }
  10. }
  11. }
  12. Type t = Type.GetType(“TestSpace.TestClass”);
  13. Object[] constructParms = new object[] {“hello”}; //構造器參數
  14. TestClass obj = (TestClass)Activator.CreateInstance(t,constructParms);
復制代碼

把參數按照順序放入一個Object數組中即可

6、如何獲取方法以及動態調用方法

  1. namespace TestSpace
  2. {
  3. public class TestClass
  4. {
  5. private string _value;
  6. public TestClass() {}
  7. public TestClass(string value)
  8. {
  9. _value = value;
  10. }
  11. public string GetValue( string prefix )
  12. {
  13. if( _value==null ) return "NULL";
  14. else return prefix+" : "+_value;
  15. }
  16. public string Value
  17. {
  18.    set
  19. {
  20. _value=value;
  21. }
  22. get
  23. {
  24. if( _value==null ) return "NULL";
  25. else return _value;
  26. }
  27. }
  28. }
  29. }
復制代碼

上面是一個簡單的類,包含一個有參數的構造器,一個GetValue的方法,一個Value屬性,我們可以通過方法的名稱來得到方法並且調用之,如:

  1. //獲取類型信息
  2. Type t = Type.GetType("TestSpace.TestClass");
  3. //構造器的參數
  4. object[] constuctParms = new object[]{"timmy"};
  5. //根據類型創建對象
  6. object dObj = Activator.CreateInstance(t,constuctParms);
  7. //獲取方法的信息
  8. MethodInfo method = t.GetMethod("GetValue");
  9. //調用方法的一些標誌位,這裏的含義是Public並且是實例方法,這也是默認的值
  10. BindingFlags flag = BindingFlags.Public | BindingFlags.Instance;
  11. //GetValue方法的參數
  12. object[] parameters = new object[]{"Hello"};
  13. //調用方法,用一個object接收返回值
  14. object returnValue = method.Invoke(dObj,flag,Type.DefaultBinder,parameters,null);
復制代碼

屬性與方法的調用大同小異,大家也可以參考MSDN

7、動態創建委托
委托是C#中實現事件的基礎,有時候不可避免的要動態的創建委托,實際上委托也是一種類型:System.Delegate,所有的委托都是從這個類派生的
System.Delegate提供了一些靜態方法來動態創建一個委托,比如一個委托:

  1. namespace TestSpace
  2. {
  3. delegate string TestDelegate(string value);
  4. public class TestClass
  5. {
  6. public TestClass() {}
  7. public void GetValue(string value) { return value;}
  8. }
  9. }
復制代碼

使用示例:

  1. TestClass obj = new TestClass();
  2. //獲取類型,實際上這裏也可以直接用typeof來獲取類型
  3. Type t = Type.GetType(“TestSpace.TestClass”);
  4. //創建代理,傳入類型、創建代理的對象以及方法名稱
  5. TestDelegate method = (TestDelegate)Delegate.CreateDelegate(t,obj,”GetValue”);
  6. String returnValue = method(“hello”);
復制代碼

---------------------------------------------------------------------------------

另外一篇關於反射的文章

---------------原文如下------------------

反射的定義:審查元數據並收集關於它的類型信息的能力。元數據(編譯以後的最基本數據單元)就是一大堆的表,當編譯程序集或者模塊時,編譯器會創建一個類定義表,一個字段定義表,和一個方法定義表等。
System.reflection命名空間包含的幾個類,允許你反射(解析)這些元數據表的代碼

System.Reflection.Assembly
System.Reflection.MemberInfo
System.Reflection.EventInfo
System.Reflection.FieldInfo
System.Reflection.MethodBase
System.Reflection.ConstructorInfo
System.Reflection.MethodInfo
System.Reflection.PropertyInfo
System.Type
以下是上面幾個類的使用方法:
(1)使用Assembly定義和加載程序集,加載在程序集清單中列出模塊,以及從此程序集中查找類型並創建該類型的實例。
(2)使用Module了解包含模塊的程序集以及模塊中的類等,還可以獲取在模塊上定義的所有全局方法或其他特定的非全局方法。
(3)使用ConstructorInfo了解構造函數的名稱、參數、訪問修飾符(如pulic 或private)和實現詳細信息(如abstract或virtual)等。使用Type的GetConstructors或 GetConstructor方法來調用特定的構造函數。
(4)使用MethodInfo了解方法的名稱、返回類型、參數、訪問修飾符(如pulic 或private)和實現詳細信息(如abstract或virtual)等。使用Type的GetMethods或GetMethod方法來調用特定的方法。
(5)使用FiedInfo了解字段的名稱、訪問修飾符(如public或private)和實現詳細信息(如static)等,並獲取或設置字段值。
(6)使用EventInfo了解事件的名稱、事件處理程序數據類型、自定義屬性、聲明類型和反射類型等,添加或移除事件處理程序。
(7)使用PropertyInfo了解屬性的名稱、數據類型、聲明類型、反射類型和只讀或可寫狀態等,獲取或設置屬性值。
(8)使用ParameterInfo了解參數的名稱、數據類型、是輸入參數還是輸出參數,以及參數在方法簽名中的位置等。
反射的層次模型:
技術分享圖片
(註:層次間都是一對多的關系)

反射的作用:
1、可以使用反射動態地創建類型的實例,將類型綁定到現有對象,或從現有對象中獲取類型
2、應用程序需要在運行時從某個特定的程序集中載入一個特定的類型,以便實現某個任務時可以用到反射。
3、反射主要應用與類庫,這些類庫需要知道一個類型的定義,以便提供更多的功能。

應用要點:
1、現實應用程序中很少有應用程序需要使用反射類型
2、使用反射動態綁定需要犧牲性能
3、有些元數據信息是不能通過反射獲取的
4、某些反射類型是專門為那些clr 開發編譯器的開發使用的,所以你要意識到不是所有的反射類型都是適合每個人的。

反射appDomain 的程序集:

當你需要反射AppDomain 中包含的所有程序集,示例如下:
static void Main
{
//通過GetAssemblies 調用appDomain的所有程序集
foreach (Assembly assem in Appdomain.currentDomain.GetAssemblies())
{
//反射當前程序集的信息
reflector.ReflectOnAssembly(assem)
}
}

說明:調用AppDomain 對象的GetAssemblies 方法 將返回一個由System.Reflection.Assembly元素組成的數組。


反射單個程序集:

上面的方法講的是反射AppDomain的所有程序集,我們可以顯示的調用其中的一個程序集,system.reflecton.assembly 類型提供了下面三種方法:
1、Load 方法:極力推薦的一種方法,Load 方法帶有一個程序集標誌並載入它,Load 將引起CLR把策略應用到程序集上,先後在全局程序集緩沖區,應用程序基目錄和私有路徑下面查找該程序集,如果找不到該程序集系統拋出異常
2、LoadFrom 方法:傳遞一個程序集文件的路徑名(包括擴展名),CLR會載入您指定的這個程序集,傳遞的這個參數不能包含任何關於版本號的信息,區域性,和公鑰信息,如果在指定路徑找不到程序集拋出異常。
3、LoadWithPartialName:永遠不要使用這個方法,因為應用程序不能確定再在載入的程序集的版本。該方法的唯一用途是幫助那些在.Net框架的測試環節使用.net 框架提供的某種行為的客戶,這個方法將最終被拋棄不用。

註意:system.AppDomain 也提供了一種Load 方法,他和Assembly的靜態Load 方法不一樣,AppDomain的load 方法是一種實例方法,返回的是一個對程序集的引用,Assembly的靜態Load 方發將程序集按值封裝發回給發出調用的AppDomain.盡量避免使用AppDomain的load 方法


利用反射獲取類型信息:

前面講完了關於程序集的反射,下面在講一下反射層次模型中的第三個層次,類型反射
一個簡單的利用反射獲取類型信息的例子:

using system;
using sytem.reflection;
class reflecting
{
static void Main(string[]args)
{
reflecting reflect=new reflecting();//定義一個新的自身類
//調用一個reflecting.exe程序集

assembly myAssembly =assembly.loadfrom(“reflecting.exe”)
reflect.getreflectioninfo(myAssembly);//獲取反射信息
}

//定義一個獲取反射內容的方法
void getreflectioninfo(assembly myassembly)
{
type[] typearr=myassemby.Gettypes();//獲取類型
foreach (type type in typearr)//針對每個類型獲取詳細信息
{
//獲取類型的結構信息
constructorinfo[] myconstructors=type.GetConstructors;

//獲取類型的字段信息
fieldinfo[] myfields=type.GetFiedls()

//獲取方法信息
MethodInfo myMethodInfo=type.GetMethods();

//獲取屬性信息
propertyInfo[] myproperties=type.GetProperties

//獲取事件信息
EventInfo[] Myevents=type.GetEvents;
}
}
}
其它幾種獲取type對象的方法:
1、System.type 參數為字符串類型,該字符串必須指定類型的完整名稱(包括其命名空間)
2、System.type 提供了兩個實例方法:GetNestedType,GetNestedTypes
3、Syetem.Reflection.Assembly 類型提供的實例方法是:GetType,GetTypes,GetExporedTypes
4、System.Reflection.Moudle 提供了這些實例方法:GetType,GetTypes,FindTypes


設置反射類型的成員:

反射類型的成員就是反射層次模型中最下面的一層數據。我們可以通過type對象的GetMembers 方法取得一個類型的成員。如果我們使用的是不帶參數的GetMembers,它只返回該類型的公共定義的靜態變量和實例成員,我們也可以通過使用帶參數的 GetMembers通過參數設置來返回指定的類型成員。具體參數參考msdn 中system.reflection.bindingflags 枚舉類型的詳細說明。

例如:
//設置需要返回的類型的成員內容
bindingFlags bf=bingdingFlags.DeclaredOnly|bingdingFlags.Nonpublic|BingdingFlags.Public;
foreach (MemberInfo mi int t.getmembers(bf))
{
writeline(mi.membertype) //輸出指定的類型成員
}


通過反射創建類型的實例:

通過反射可以獲取程序集的類型,我們就可以根據獲得的程序集類型來創建該類型新的實例,這也是前面提到的在運行時創建對象實現晚綁定的功能
我們可以通過下面的幾個方法實現:
1、System.Activator 的CreateInstance方法。該方法返回新對象的引用。具體使用方法參見msdn
2、System.Activator 的createInstanceFrom 與上一個方法類似,不過需要指定類型及其程序集
3、System.Appdomain 的方法:createInstance,CreateInstanceAndUnwrap,CreateInstranceFrom和CreateInstraceFromAndUnwrap
4、System.type的InvokeMember實例方法:這個方法返回一個與傳入參數相符的構造函數,並構造該類型。
5、System.reflection.constructinfo 的Invoke實例方法

反射類型的接口:

如果你想要獲得一個類型繼承的所有接口集合,可以調用Type的FindInterfaces GetInterface或者GetInterfaces。所有這些方法只能返回該類型直接繼承的接口,他們不會返回從一個接口繼承下來的接口。要想返回接口的基礎接口必須再次調用上述方法。


反射的性能:

使用反射來調用類型或者觸發方法,或者訪問一個字段或者屬性時clr 需要做更多的工作:校驗參數,檢查權限等等,所以速度是非常慢的。所以盡量不要使用反射進行編程,對於打算編寫一個動態構造類型(晚綁定)的應用程序,可以采取以下的幾種方式進行代替:
1、通過類的繼承關系。讓該類型從一個編譯時可知的基礎類型派生出來,在運行時生成該類型的一個實例,將對其的引用放到其基礎類型的一個變量中,然後調用該基礎類型的虛方法。
2、通過接口實現。在運行時,構建該類型的一個實例,將對其的引用放到其接口類型的一個變量中,然後調用該接口定義的虛方法。
3、通過委托實現。讓該類型實現一個方法,其名稱和原型都與一個在編譯時就已知的委托相符。在運行時先構造該類型的實例,然後在用該方法的對象及名稱構造出該委托的實例,接著通過委托調用你想要的方法。這個方法相對與前面兩個方法所作的工作要多一些,效率更低一些。

個人操作方案:

源DLL類:

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI;
using System.Collections;


namespace cn.SwordYang
{

public class TextClass:System.Web.UI.Page
{

public static void RunJs(Page _page, string Source)
{
_page.ClientScript.RegisterStartupScript(_page.GetType(), "", "<script type=\"text/javascript\">" + Source + ";</script>");

}

}

}

//調用代碼

System.Reflection.Assembly ass = Assembly.LoadFrom(Server.MapPath("bin/swordyang.dll")); //加載DLL
System.Type t = ass.GetType("cn.SwordYang.TextClass");//獲得類型
object o = System.Activator.CreateInstance(t);//創建實例

System.Reflection.MethodInfo mi = t.GetMethod("RunJs");//獲得方法


mi.Invoke(o, new object[] { this.Page,"alert(‘測試反射機制‘)"});//調用方法

反射機制對應設計模式中的策略模式。

文章轉載自https://www.cnblogs.com/wangshenhe/p/3256657.html

(轉)c#反射