C#反射(Reflection)詳解

 

一、 什麼是反射
二、 命名空間與裝配件的關係
三、 運行期獲得類型信息有什麼用
四、 如何使用反射獲取類型
五、 如何根據類型來動態建立對象
六、 如何獲取方法以及動態調用方法
七、 動態建立委託

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

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

二、命名空間與裝配件的關係
        不少人對這個概念可能仍是很不清晰,對於合格的.Net程序員,有必要對這點進行澄清。
        命名空間相似與Java的包,但又不徹底等同,由於Java的包必須按照目錄結構來放置,命名空間則不須要。

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

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

namespace  N1
{
      public  class  AC1  {…}
      public  class  AC2  {…}
}
namespace  N2
{
      public  class  AC3  {…}
      public  class  AC4{…}
}

 

裝配件B:java

namespace  N1
{
      public  class  BC1  {…}
      public  class  BC2  {…}
}
namespace  N2
{
      public  class  BC3  {…}
      public  class  BC4{…}
}

 

這兩個裝配件中都有N1和N2兩個命名空間,並且各聲明瞭兩個類,這樣是徹底能夠的,而後咱們在一個應用程序中引用裝配件A,那麼在這個應用程序中,咱們能看到N1下面的類爲AC1和AC2,N2下面的類爲AC3和AC4。
        接着咱們去掉對A的引用,加上對B的引用,那麼咱們在這個應用程序下能看到的N1下面的類變成了BC1和BC2,N2下面也同樣。
        若是咱們同時引用這兩個裝配件,那麼N1下面咱們就能看到四個類:AC一、AC二、BC1和BC2。

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

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

三、運行期獲得類型信息有什麼用
        有人也許疑問,既然在開發時就可以寫好代碼,幹嗎還放到運行期去作,不光繁瑣,並且效率也受影響。
這就是個見仁見智的問題了,就跟早綁定和晚綁定同樣,應用到不一樣的場合。有的人反對晚綁定,理由是損耗效率,可是不少人在享受虛函數帶來的好處的時侯尚未意識到他已經用上了晚綁定。這個問題說開去,不是三言兩語能講清楚的,因此就點到爲止了。
        個人見解是,晚綁定可以帶來不少設計上的便利,合適的使用可以大大提升程序的複用性和靈活性,可是任何東西都有兩面性,使用的時侯,須要再三衡量。

接着說,運行期獲得類型信息到底有什麼用呢?
仍是舉個例子來講明,不少軟件開發者喜歡在本身的軟件中留下一些接口,其餘人能夠編寫一些插件來擴充軟件的功能,好比我有一個媒體播放器,我但願之後能夠很方便的擴展識別的格式,那麼我聲明一個接口:程序員

public  interface  IMediaFormat
{
string  Extension  {get;}
Decoder  GetDecoder();
}

這個接口中包含一個Extension屬性,這個屬性返回支持的擴展名,另外一個方法返回一個解碼器的對象(這裏我假設了一個Decoder的類,這個類提供把文件流解碼的功能,擴展插件能夠派生之),經過解碼器對象我就能夠解釋文件流。
那麼我規定全部的解碼插件都必須派生一個解碼器,而且實現這個接口,在GetDecoder方法中返回解碼器對象,而且將其類型的名稱配置到個人配置文件裏面。
這樣的話,我就不須要在開發播放器的時侯知道未來擴展的格式的類型,只須要從配置文件中獲取如今全部解碼器的類型名稱,而動態的建立媒體格式的對象,將其轉換爲IMediaFormat接口來使用。

這就是一個反射的典型應用。


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

…
public  void  Process(  object  processObj  )
{
Type  t  =  processsObj.GetType();
if(  t.GetInterface(「ITest」)  !=null  )
                    …
}
…

 

另一種獲取類型的方法是經過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的回答很精彩


五、如何根據類型來動態建立對象
        System.Activator提供了方法來根據類型動態建立對象,好比建立一個DataTable:設計模式

Type  t  =  Type.GetType("System.Data.DataTable,System.Data,Version=1.0.3300.0,  Culture=neutral,  PublicKeyToken=b77a5c561934e089");
DataTable  table  =  (DataTable)Activator.CreateInstance(t);

 

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

namespace  TestSpace  
{
  public  class  TestClass
      {
      private  string  _value;
      public  TestClass(string  value)  
    {
      _value=value;
      }
  }
}
…
Type  t  =  Type.GetType(「TestSpace.TestClass」);
Object[]  constructParms  =  new  object[]  {「hello」};  //構造器參數
TestClass  obj  =  (TestClass)Activator.CreateInstance(t,constructParms);
…

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


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

namespace  TestSpace
{
      public  class  TestClass  {
          private  string  _value;
          public  TestClass()  
          {
          }

          public  TestClass(string  value)  {
                _value  =  value;
          }

          public  string  GetValue(  string  prefix  ) 
          {
            if(  _value==null  )
              return  "NULL";
            else
              return  prefix+"  :  "+_value;
          }

          public  string  Value  
          {
            set  
            {
              _value=value;
            }
            get  
            {
             if(  _value==null  )
               return  "NULL";
             else
               return  _value;
            }
          }
      }
}

上面是一個簡單的類,包含一個有參數的構造器,一個GetValue的方法,一個Value屬性,咱們能夠經過方法的名稱來獲得方法而且調用之,如:框架

//獲取類型信息
Type  t  =  Type.GetType("TestSpace.TestClass");
//構造器的參數
object[]  constuctParms  =  new  object[]{"timmy"};
//根據類型建立對象
object  dObj  =  Activator.CreateInstance(t,constuctParms);
//獲取方法的信息
MethodInfo  method  =  t.GetMethod("GetValue");
//調用方法的一些標誌位,這裏的含義是Public而且是實例方法,這也是默認的值
BindingFlags  flag  =  BindingFlags.Public  |  BindingFlags.Instance;
//GetValue方法的參數
object[]  parameters  =  new  object[]{"Hello"};
//調用方法,用一個object接收返回值
object  returnValue  =  method.Invoke(dObj,flag,Type.DefaultBinder,parameters,null);

屬性與方法的調用大同小異,你們也能夠參考MSDN

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

namespace  TestSpace  {
  delegate  string  TestDelegate(string  value);
  public  class  TestClass  
  {
    public  TestClass()  
    {
                  
    }

    public  void  GetValue(string  value)  
   {
     return  value;
   }
 }

}

使用示例:函數

TestClass  obj  =  new  TestClass();

//獲取類型,實際上這裏也能夠直接用typeof來獲取類型
Type  t  =  Type.GetType(「TestSpace.TestClass」);
//建立代理,傳入類型、建立代理的對象以及方法名稱
TestDelegate  method  =  (TestDelegate)Delegate.CreateDelegate(t,obj,」GetValue」);

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瞭解參數的名稱、數據類型、是輸入參數仍是輸出參數,以及參數在方法簽名中的位置等。
反射的層次模型:

(注:層次間都是一對多的關係)

 

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

應用要點:
一、現實應用程序中不多有應用程序須要使用反射類型
二、使用反射動態綁定須要犧牲性能
三、有些元數據信息是不能經過反射獲取的
四、某些反射類型是專門爲那些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 類型提供了下面三種方法:
一、Load 方法:極力推薦的一種方法,Load 方法帶有一個程序集標誌並載入它,Load 將引發CLR把策略應用到程序集上,前後在全局程序集緩衝區,應用程序基目錄和私有路徑下面查找該程序集,若是找不到該程序集系統拋出異常
二、LoadFrom 方法:傳遞一個程序集文件的路徑名(包括擴展名),CLR會載入您指定的這個程序集,傳遞的這個參數不能包含任何關於版本號的信息,區域性,和公鑰信息,若是在指定路徑找不到程序集拋出異常。
三、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對象的方法:
一、System.type   參數爲字符串類型,該字符串必須指定類型的完整名稱(包括其命名空間)
二、System.type 提供了兩個實例方法:GetNestedType,GetNestedTypes
三、Syetem.Reflection.Assembly 類型提供的實例方法是:GetType,GetTypes,GetExporedTypes
四、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)    //輸出指定的類型成員
}


經過反射建立類型的實例:

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

反射類型的接口:

若是你想要得到一個類型繼承的全部接口集合,能夠調用Type的FindInterfaces GetInterface或者GetInterfaces。全部這些方法只能返回該類型直接繼承的接口,他們不會返回從一個接口繼承下來的接口。要想返回接口的基礎接口必須再次調用上述方法。


反射的性能:

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

 

我的操做方案:

源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('測試反射機制')"});//調用方法

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

相關文章
相關標籤/搜索