反射

反射(Reflection)是.NET中的重要機制,通過放射,可以在運行時獲得.NET中每一個類型(包括類、結構、委託、接口和枚舉等)的成員,包括方法、屬性、事件,以及構造函數等。還可以獲得每個成員的名稱、限定符和參數等。有了反射,即可對每一個類型瞭如指掌。如果獲得了構造函數的信息,即可直接創建對象,即使這個對象的類型在編譯時還不知道。



程序代碼在編譯後生成可執行的應用,我們首先要了解這種可執行應用程序的結構。

應用程序結構分爲應用程序域—程序集—模塊—類型—成員幾個層次,公共語言運行庫加載器管理應用程序域,這種管理包括將每個程序集加載到相應的應用程序域以及控制每個程序集中類型層次結構的內存佈局。

程序集包含模塊,而模塊包含類型,類型又包含成員,反射則提供了封裝程序集、模塊和類型的對象。我們可以使用反射動態地創建類型的實例,將類型綁定到現有對象或從現有對象中獲取類型,然後調用類型的方法或訪問其字段和屬性。反射通常具有以下用途。

(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瞭解參數的名稱、數據類型、是輸入參數還是輸出參數,以及參數在方法簽名中的位置等。

System.Reflection.Emit命名空間的類提供了一種特殊形式的反射,可以在運行時構造類型。
反射也可用於創建稱爲類型瀏覽器的應用程序,使用戶能夠選擇類型,然後查看有關選定類型的信息。
此外,Jscript等語言編譯器使用反射來構造符號表。System.Runtime.Serialization命名空間中的類使用反射來訪問數據並確定要永久保存的字段,System.Runtime.Remoting命名空間中的類通過序列化來間接地使用反射。
2008年03月01日 星期六 下午 07:36
[來源]http://blog.csdn.net/sscsgss/archive/2006/10/19/1341035.aspx
提綱:
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:
namespace   N1
{
      public   class   AC1   {…}
      public   class   AC2   {…}
}
namespace   N2
{
      public   class   AC3   {…}
      public   class   AC4{…}
}
裝配件B:
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下面我們就能看到四個類:AC1、AC2、BC1和BC2。
        到這裏,我們可以清楚一個概念了,命名空間只是說明一個類型是那個族的,比如有人是漢族、有人是回族;而裝配件表明一個類型住在哪裏,比如有人住在北京、有人住在上海;那麼北京有漢族人,也有回族人,上海有漢族人,也有回族人,這是不矛盾的。
        上面我們說了,裝配件是一個類型居住的地方,那麼在一個程序中要使用一個類,就必須告訴編譯器這個類住在哪兒,編譯器才能找到它,也就是說必須引用該裝配件。
        那麼如果在編寫程序的時候,也許不確定這個類在哪裏,僅僅只是知道它的名稱,就不能使用了嗎?答案是可以,這就是反射了,就是在程序運行的時候提供該類型的地址,而去找到它。
有興趣的話,接着往下看吧。
3、運行期得到類型信息有什麼用
    有人也許疑問,既然在開發時就能夠寫好代碼,幹嘛還放到運行期去做,不光繁瑣,而且效率也受影響。
這就是個見仁見智的問題了,就跟早綁定和晚綁定一樣,應用到不同的場合。有的人反對晚綁定,理由是損耗效率,但是很多人在享受虛函數帶來的好處的時侯還沒有意識到他已經用上了晚綁定。這個問題說開去,不是三言兩語能講清楚的,所以就點到爲止了。
    我的看法是,晚綁定能夠帶來很多設計上的便利,合適的使用能夠大大提高程序的複用性和靈活性,但是任何東西都有兩面性,使用的時侯,需要再三衡量。
接着說,運行期得到類型信息到底有什麼用呢?
還是舉個例子來說明,很多軟件開發者喜歡在自己的軟件中留下一些接口,其他人可以編寫一些插件來擴充軟件的功能,比如我有一個媒體播放器,我希望以後可以很方便的擴展識別的格式,那麼我聲明一個接口:
public   interface   IMediaFormat
{
    string   Extension   {get;}
    Decoder   GetDecoder();
}
這個接口中包含一個Extension屬性,這個屬性返回支持的擴展名,另一個方法返回一個解碼器的對象(這裏我假設了一個Decoder的類,這個類提供把文件流解碼的功能,擴展插件可以派生之),通過解碼器對象我就可以解釋文件流。
那麼我規定所有的解碼插件都必須派生一個解碼器,並且實現這個接口,在GetDecoder方法中返回解碼器對象,並且將其類型的名稱配置到我的配置文件裏面。
這樣的話,我就不需要在開發播放器的時侯知道將來擴展的格式的類型,只需要從配置文件中獲取現在所有解碼器的類型名稱,而動態的創建媒體格式的對象,將其轉換爲IMediaFormat接口來使用。
這就是一個反射的典型應用。

4、如何使用反射獲取類型
    首先我們來看如何獲得類型信息。
    獲得類型信息有兩種方法,一種是得到實例對象
    這個時侯我僅僅是得到這個實例對象,得到的方式也許是一個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/topic/2210/2210762.xml?temp=.1919977    qqchen的回答很精彩

5、如何根據類型來動態創建對象
    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數組中即可

6、如何獲取方法以及動態調用方法
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

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

到這裏,我們簡單的講述了反射的作用以及一些基本的用法,還有很多方面沒有涉及到,有興趣的朋友可以參考MSDN。
   很奇怪,很多人都不願看MSDN,其實你想要的答案,99%都可以在裏面找到。

 

 

 c#中的反射   star.gifuploading.4e448015.gif轉存失敗重新上傳取消star.gifuploading.4e448015.gif轉存失敗重新上傳取消star.gifuploading.4e448015.gif轉存失敗重新上傳取消star.gifuploading.4e448015.gif轉存失敗重新上傳取消star.gifuploading.4e448015.gif轉存失敗重新上傳取消star.gifuploading.4e448015.gif轉存失敗重新上傳取消star.gifuploading.4e448015.gif轉存失敗重新上傳取消star.gifuploading.4e448015.gif轉存失敗重新上傳取消star.gifuploading.4e448015.gif轉存失敗重新上傳取消star.gifuploading.4e448015.gif轉存失敗重新上傳取消star.gifuploading.4e448015.gif轉存失敗重新上傳取消star.gifuploading.4e448015.gif轉存失敗重新上傳取消star_half.gifuploading.4e448015.gif轉存失敗重新上傳取消star_half.gifuploading.4e448015.gif轉存失敗重新上傳取消star_half.gifuploading.4e448015.gif轉存失敗重新上傳取消  ask.gifuploading.4e448015.gif轉存失敗重新上傳取消ask.gifuploading.4e448015.gif轉存失敗重新上傳取消ask.gifuploading.4e448015.gif轉存失敗重新上傳取消CSDN Blog推出文章指數概念,文章指數是對Blog文章綜合評分後推算出的,綜合評分項分別是該文章的點擊量,回覆次數,被網摘收錄數量,文章長度和文章類型;滿分100,每月更新一次。

c#中的反射 作者: YAOTIEBING
反射的概述
  
  反射的定義:審查元數據並收集關於它的類型信息的能力。元數據(編譯以後的最基本數據單元)就是一大堆的表,當編譯程序集或者模塊時,編譯器會創建一個類定義表,一個字段定義表,和一個方法定義表等,。System.reflection命名空間包含的幾個類,允許你反射(解析)這些元數據表的代碼
  
  和反射相關的命名空間(我們就是通過這幾個命名空間訪問反射信息):
  
  System.Reflection.MemberInfo
  
   System.Reflection.EventInfo
  
   System.Reflection.FieldInfo
  
   System.Reflection.MethodBase
  
   System.Reflection.ConstructorInfo
  
   System.Reflection.MethodInfo
  
   System.Reflection.PropertyInfo
  
   System.Type
  
   System.Reflection.Assembly
  
  反射的層次模型:
  
  CSDN_Dev_Image_2004-6-21513070.jpguploading.4e448015.gif轉存失敗重新上傳取消CSDN_Dev_Image_2004-6-21513070.jpguploading.4e448015.gif轉存失敗重新上傳取消CSDN_Dev_Image_2004-6-21513070.jpguploading.4e448015.gif轉存失敗重新上傳取消
  注:層次間都是一對多的關係
  
  反射的作用:
  
  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方法。該方法返回新對象的引用。具體使用方法參見msnd
  
  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.通過委託實現。讓該類型實現一個方法,其名稱和原型都與一個在編譯時就已知的委託相符。在運行時先構造該類型的實例,然後在用該方法的對象及名稱構造出該委託的實例,接着通過委託調用你想要的方法。這個方法相對與前面兩個方法所作的工作要多一些,效率更低一些
上次在MSDN網站看到一個比較動態調用代碼的文章,用到的例子似乎比較複雜,爲計算一個複雜多項式子而將其中部分割開,動態形成代碼段來被循環調用。詳細看.NET下幾種動態生成代碼方式比較。今天看到微軟C#團隊的Eric Gunnerson寫的另外一篇關於動態調用代碼性能的比較文章,爲了說明結果和計算的準確性,減少由於函數複雜而受編譯優化的影響,他使用了一個極爲簡單的例子:
輸入一個參數,然後返回這個參數加一,這麼簡單的函數,優化和沒有優化的代碼應該不會有差別的了。
    public class Processor
    
{
        
public int Process(int value)
        
{
            
return value + 1;
        }

    }


而對比方面,除了上次那幾種外,還加了代理方式調用來進行比較。
1. 直接調用
int value = processor.Process(i);
2. 用反射機制,Type.InvokeMember()調用。
    Type t = typeof(Processor);
    
int value = 
        (
int) t.InvokeMember(
                  
"Process"
         BindingFlags.Instance 
| BindingFlags.Public | 
                  BindingFlags.InvokeMethod, 
                  
null, processor, new object[] {i});

3. 通過一個接口
    public interface IProcessor
   
{
        
int Process(int value);
    }


4. 通過一個委託Delegate
    public delegate int ProcessCaller(int value);
    ProcessCaller processCaller 
= new ProcessCaller(processor.Process);
    
int value = processCaller(i); 

5. 也通過反射機制建立委託再動態調用
    Type delegateType = CreateCustomDelegate(methodInfo);
    Delegate p 
= Delegate.CreateDelegate(delegateType, 
                                         process, 
"Process");
    
int value = (int) p.DynamicInvoke(new object[] {i});

6. 元編程方式
對於2和5由於使用反射機制,不可避免需要建立中間的臨時對象去傳遞參數,將參數和返回值裝箱等操作,因此花費了大量的機器時間。

下面是運行的某次結果(循環100000次):

 



結論:
1.直接調用速度最快是肯定的。
2.接口調用比元編程速度快,而元編程又比委託方式快,但微軟相信Whidbey會極大優化委託調用方式,從而使它接近接口調用的水平。
3.直接用Type的反射機制是速度最慢的,比用反射機制建立委託來動態調用還慢。
4.直接使用委託不夠靈活,有時候需要用反射機制建立委託來調用,但會減低性能,希望Whidbey優化了委託的性能後這種情況可以改善,靈活是需要犧牲性能的。
 
在實際開發中,我們經常需要從數據庫中讀取數據並賦值給實體類的相應屬性。在.Text的DataDTOProvider中存在大量這樣的代碼, 比如:
public Role[] GetRoles(int BlogID)
        
{
            System.Collections.ArrayList al
=new System.Collections.ArrayList();
            IDataReader reader
=DbProvider.Instance().GetRoles(BlogID);
            
try
            {
                
while(reader.Read())
                
{
                    Role role
=new Role();
                    
if(reader["RoleID"]!=DBNull.Value)
                    
{
                        role.RoleID
=(int)reader["RoleID"];
                    }

                    if(reader["Name"]!=DBNull.Value)
                    
{
                        role.Name
=(string)reader["Name"];
                    }

                    if(reader["Description"]!=DBNull.Value)
                    
{
                        role.Description
=(string)reader["Description"];
                    }

                    //ReaderToObject(reader,role);
                    al.Add(role);
                }

            }
            finally
            {
                reader.Close();
            }

            return (Role[])al.ToArray(typeof(Role));
        
        }

對於上面的代碼,我覺得有幾點不優雅之處:
1、每次對Role的屬性進行賦值時,都要檢查reader的值是否爲DBNull,出現了很多重複代碼
2、每次對Role的屬性進行賦值時,都要進行類型轉換, 而Role屬性的類型是已知的,是不是可以自動完成這樣的轉換?
3、每次對Role的屬性進行賦值時,都要進行Role屬性與數據庫字段的對應。如果我們在設計數據庫與實體類時,保證數據庫字段與實體類屬性採用同樣的名稱,那利用反射,我們可以通過代碼自動進行屬性與字段的對應。即使數據庫字段與屬性不同名,我們也可以通過更改查詢語句,來做到這一點。
是不是可以對上面的代碼進行改進,使代碼變得更優雅?那優雅的代碼應該是什麼樣的呢?如果我們用上面代碼中註釋的代碼行ReaderToObject(reader,role);取代它之前的對Role屬性進行賦值的語句,是不是會使代碼變得更優雅?ReaderToObject的作用就是自動完成將reader中的值寫入到role中對應的屬性中(前提是reader中的字段與role中對應的屬性具有相同的名稱)。現在我們的任務就是實現ReaderToObject, 有了強大的武器—Reflection,我們的任務就變得很輕鬆, 也不多說了,下面的代碼是我的實現方法:
private void ReaderToObject(IDataReader reader,object targetObj)
        
{
            
for(int i=0;i<reader.FieldCount;i++)
            
{
                System.Reflection.PropertyInfo propertyInfo
=targetObj.GetType().GetProperty(reader.GetName(i));
                
if(propertyInfo!=null)
                
{
                    
if(reader.GetValue(i)!=DBNull.Value)
                    
{
                        propertyInfo.SetValue(targetObj,reader.GetValue(i),
null);
                    }

                }
            }
        }
ReaderToObject可以將reader中的數據讀入到任何實體類中。數據庫字段與實體類屬性的映射原則是名稱相同。當然,我們也可以通過配置文件來進行兩者映射。

    個人想法:在開發中,面對那麼多設計思想和設計模式,常常令人感到迷惑,當你把更多的精力放在選用哪個設計思想或設計模式時,我覺得不要忽略很重要的一點,儘可能地減少重複代碼,只要我們能有效地減少重複代碼,我們採用的方法就是好方法,而不要太在乎採用了哪種模式。就像獨孤九劍,正因爲擺脫了傳統招式的束縛,才能戰無不勝!
先來看這段NUnit測試代碼,我們希望用反射機制在運行時訪問一個對象的枚舉類型的域或屬性:
 
[TestFixture]
public class PaymentInfo
{
 public enum PaymentType
 {
    Cash, CreditCard, Check
 }

 public PaymentType Type;

 public void Test()
 {
    PaymentInfo payment = new PaymentInfo();
    payment.Type = PaymentType.Cash;

    System.Reflection.FieldInfo enumField = GetType().GetField("Type");

    int paymentTypeInt32;

    paymentTypeInt32 = (int)enumField.GetValue(payment);
    Assert.AreEqual((int)PaymentType.Cash, paymentTypeInt32);

    enumField.SetValue(payment, paymentTypeInt32);
    Assert.AreEqual(PaymentType.Cash, payment.Type);
 }
}
 
 
在這個測試中,使之通過的辦法其實非常簡單:把劃線部分強制轉換爲枚舉類型即可,如:(PaymentType)paymentTypeInt32。可問題是:在運行時如何動態轉換類型呢?比如說我在寫ElegantDAL的時候,需要將從數據庫讀出的一個類型爲int的數值寫入到要返回的對象的一個枚舉型字段中,此時我只有fieldInfo、columnValue和resultObject,然而寫成fieldInfo.SetValue(resultObject, columnValue)就會出現前面提到的錯誤,可是我又只有一個運行時的Type信息(fieldInfo.FieldType),我又不能寫成fieldInfo.SetValue(resultObject, (fieldInfo.FieldType)columnValue)……
 
只好將這種情況列爲一個特例處理,而我們的救兵則是Enum.ToObject()方法——你知道有更好的方法解決這個問題嗎?
 
BTW: 很多朋友寫信來問偶承諾的下一篇文章什麼時候才能搞定,其實我也不想拖,可最近工作確實比較緊張,而我的最佳寫作時間又都在深更半夜(之前還要熱身進入狀態),所以遲遲不能結稿。目前其實已經寫了好多(已經和第一篇長度差不多了),可是發現要寫的內容還太多(也許是我寫得太細了,因爲我的目標不僅是講how,更多的是what和why),所以現在決定把本篇文字再細分爲兩部分(上篇只講TP/RP+MBR對象;把CBO相關的內容放到下篇再寫),以便能夠儘快讓大家看到新的內容——嗯,爭取週五release吧。至於還有朋友希望我能夠深入寫些關於ElegantDAL的設計與實現細節,這個留待稍後吧,正好我們將要整理的項目文檔中也要有這一塊內容(現在在我們這個企業級項目中用得還真挺好),到時候一起寫啦(希望能夠和canyue儘快完成第三版本的重寫工作,目前使用的是第二版)。
實際上運行測試時發現在標紅的這行上拋出一個異常:“對象類型無法轉換爲目標類型”。究其原因,原來是因爲CLR的反射機制不允許枚舉類型與整數類型之間隱式轉換。不過C#編譯器還是允許我們通過強制類型轉換的語法來進行兩者間的顯式轉換。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章