反射

1、基本概念
反射
:反射是一個運行庫類型發現的過程。 經過反射能夠獲得一個給定程序集所包含的全部類型的列表,這個列表包括給定類型中定義的方法、字段、屬性和事件。也能夠動態的發現一組給定類支持的藉口、方法的參數和其餘相關信息如基類、命名空間、數據清單等。
  
2、命名空間
         
     1.System.Reflection
命名空間內的各種型
            
1Assembly    經過它能夠加載、瞭解和操縱一個程序集
              (2) AssemblyName 
經過它能夠找到大量隱藏在程序集的身份中的信息,如版本信息、區域信息等
              (3) EventInfo  
事件的信息
              (4) FieldInfo  
字段的信息
              (5) MethodInfo  
方法的信息
              (6) ParameterInfo  
參數的信息
              (7
PropertyInfo  屬性的信息
              (8) MemberInfo  是抽象基類
,爲  EventInfoFieldInfo MethodInfoPropertyInfo等類型定義了公共的行爲。    
              (9) Module 
用來訪問帶有多文件程序集的給定模塊  
      
      2.System.Type
 
         System.Type
支持的成員能夠分爲這樣幾類
               (1) Is***   
用來檢查一個類型的元數據,如IsAbstractIsClassIsValueType等等
               (2) Get*** 
用來從類型獲得指定項目,如GetEvent()獲得類型的一個指定的事件(EventInfo)。 另外,這些方法都有一個單數版本和一個複數版本。如GetEvent()對應有一個複數版             GetEvents() 該方法返回一個相關的EventInfo數組 
                (3) FindMembers()   根據查詢條件
返回一個MemberInfo類型的數組
                (4)GetType()  
該靜態方法根據一個字符串名稱返回一個Type實例
                (5)InvokeMember()  
對給定項目進行晚期綁定

      3.獲得一個Type類型實例的三種方法
(由於Type是一個抽象類,因此不能直接使用new關鍵字建立一個Type對象)
                (1) 
使用System.Object.GetType()
                                    e.g:    Person pe=new Person();  //---------
定義peperson類的一個對象 
                                              Type t=pe.GetType(); 
                 (2)
使用System.Type.GetType()靜態方法,參數爲類型的徹底限定名
                                    e.g:     Type t=Type.GetType("Entity.Person");
                                               
該方法被重載,容許指定兩個布爾類型的參數,一個用來控制當前類型不能找到時是否拋出異常,
                        
另外一個用來指示是否區分字符串大小寫                         
                                                e.g:    
程序員

Type t=Type.GetType("Entity.Person",false,true);
                                 注意到傳入的字符串並無包含類型所在的程序集信息,
此時該類型便被認爲是定義在當前執行的程序集中的
                                 
要獲得一個外部私有程序集的類型元數據時,字符串參數必須使用類型徹底限定名加上類型所在程序集的友好名字
                         e.g:  Type t=Type.GetType("Entity.Person","Entity");//------"Entity"
即爲類型所在程序集的友好名字 
                             
嵌套類型:傳入的字符串能夠指定一個+標記來表示一個嵌套類型,如但願獲得一個嵌套在person類中的枚舉類型City的類型信息,
                           
則能夠這樣  e.g:   Type t=Type.GetType("Entity.person+City");
                       (3) 
使用typeof運算符         e.g:   Type  t=typeof(person);
                  
                  
三種方法的比較:使用第一種方法必須先創建一個實例,然後兩種方法沒必要先創建實例。
                  但使用typeof運算符仍然須要知道類型的編譯時信息,而使用System.Type.GetType()靜態方法
                  不須要知道類型的編譯時信息,因此是首選方法。

web

----------------♧--------------♧--------------♧--------♧------------♧----------♧----------------------♧--------------♧下面是一個實例,簡單的運用了前面介紹的知識,實現了對一個Type對象的反射,包括反射其全部可見字段、方法、屬性、事件。反射類型的基本屬性。並將其中一個方法的詳細信息列了出來
【源代碼】
編程

 

複製代碼
  1 using  System;
  2 using  System.Collections.Generic;
  3 using  System.Linq;
  4 using  System.Text;
  5 using  System.Reflection;
  6
  7 using  System.Collections;  // --------要實現IEnumerable接口則必須制定該命名空間
  8
  9
 10 namespace  Exercise
 11 {
 12    public class BasePerson //-------------假設一個基類,定義了一個公共方法和一個私有方法
 13    {
 14        public void BasePublic()
 15        {
 16        }

 17
 18        private void BasePrivate()
 19        {
 20        }

 21    
 22    }
;
 23
 24
 25    //Person類實現了接口IEnumerable,使得類中定義的Array數組可以使用foreach枚舉
 26    public class Person :BasePerson, IEnumerable 
 27    {
 28        private string name = "林田惠"//---姓名
 29        public  int age=20;     //---年齡
 30
 31        Array children=null;//---子女數組
 32
 33        Person()
 34        {
 35        }

 36
 37        Person(string a,int b)
 38        {
 39            Name = a;
 40            Age = b;
 41        }

 42
 43     
 44        public string Name 
 45        {
 46            get return name; }
 47            set { }
 48        }

 49       
 50        public int Age
 51        {
 52            get return age; }
 53            set { }
 54        }

 55
 56        public void AddAge()//---自增一歲的方法
 57        {
 58             Age+=1;
 59        }

 60
 61        
 62        public delegate void PersonNameHandler(string x);
 63        public event PersonNameHandler OnChangeName; //------定義了一個改變姓名的事件
 64        
 65        public void ChangeName(string nam)//---更名的方法
 66        {
 67            Name = nam;
 68        }

 69
 70        public void  ChangeNameAndAddAge(string name,int age)//------具備兩個參數的方法,用來演示反射具體方法的詳細狀況
 71        {
 72            this.Name = name;
 73            this.Age += age;
 74        }

 75
 76        public IEnumerator GetEnumerator()//---實現接口
 77        {
 78            return children.GetEnumerator();
 79        }

 80
 81    }

 82
 83  
 84
 85    public class Program
 86    {
 87        
 88        
 89        構建自定義元數據查看器
180
181        public static void Main(string[] args)
182        {
183
184            Console.WriteLine("----------------------------------------------------------------");
185
186            Type t = Type.GetType("Exercise.Person");//-------Person類的徹底限定名爲"Exercise.Person"
187            
188            ListOtherInfo(t);//反射其餘一些信息     
189            ListFields(t);//反射字段
190            ListProperties(t);//反射屬性
191            ListInterFaces(t);//反射接口
192            ListEvents(t);//反射事件
193            ListMethodDetail(t, "ChangeNameAndAddAge");//反射一個特定方法的詳細信息
194            ListMethods(t);//反射方法
195            
196            
197            Console.ReadLine();   
198        }

199    }

200}

201
202
複製代碼

【實現效果】

【總結】結合源代碼和運行效果,總結以下
            1.Name屬性在編譯後成爲了get_Name()set_Name()兩個獨立的方法
            2.OnChangeName事件的註冊(+=)和取消註冊(-=)分別成爲了add_ OnChangeName ()remove_ OnChangeName方法
            3.私有(private)字段name 沒有被打印出來
            4.基類的基類System.Object的成員GetType()Equals()也被打印了出來,基類的共有方法也被打印出來
數組


爲了更好的控制顯示咱們所想要的信息,下面簡單介紹一下
FindMembers() 方法。安全

MemberInfo[] mi = t.FindMembers(                   //【 FindMembers 】
    MemberTypes.Method,             //【說明查找的成員類型爲 Method】
    BindingFlags.Public |
    BindingFlags.Static |
    BindingFlags.NonPublic |        //【位屏蔽】
    BindingFlags.Instance |
    BindingFlags.DeclaredOnly,     函數

Type.FilterName,         //執行比較的委託       工具

 "*"
);
開發工具

將上例的ListMethods方法改成
    public static void ListMethods(Type t)
        {
            Console.WriteLine(""n該類型的全部方法:");
            MemberInfo[] mi = t.FindMembers(      //【 FindMembers 】
                 MemberTypes.Method,             //【說明查找的成員類型爲 Method】
                 BindingFlags.Public |
                 BindingFlags.Static |
                 BindingFlags.NonPublic |        //【位屏蔽】
                 BindingFlags.Instance |
                 BindingFlags.DeclaredOnly,
                 Type.FilterName,         //【執行比較的委託】       
               "*"
             );
 
            foreach (MethodInfo m in mi)
            {
                Console.WriteLine(""t方法名:{0}", m.Name);
            }
 }

Type.FilterName 返回一個MemberFilter類型的委託,它說明按照方法名稱進行過濾,最後一個參數「*」,說明返回全部名稱(若是使用「Get*」,則會返回全部以Get開頭的方法)

如今的輸出以下:
this


能夠看到,全部繼承而來的方法都沒有顯示。。
 
spa

經過以上實例能夠看到System.Reflection命名空間和System.Type類容許咱們反射Type實例的大量信息。

然而,對於當前建立的這個實例有一個很大的限制——僅僅能訪問當前的程序集,因此接下來要討論:

如何能使應用程序加載並反射在運行時並不知道的程序集?

1.動態加載程序集

       在不少時候咱們須要在運行時以編程的方式動態載入程序集,即便那些程序集沒有記錄在程序清單中。

       按需加載外部程序集的操做被稱爲動態加載

       System.Reflection提供了一個名爲Assembly的類,使用它咱們能夠:

1)動態加載程序集並找到關於程序集自身的屬性

2)動態加載私有或共享程序集

3)加載任意位置的程序集

從本質上說,Assembly類提供的方法(尤爲是Load()和LoadFrom())使得咱們能夠用編程的方

式提供和客戶端.config文件中一樣的信息

下面經過一個實例來演示如何經過一個程序集的友好名稱來動態加載一個程序集,並打印出其所

包含的每一個類、接口、委託等等信息。

【源代碼】

 

複製代碼
using  System;
using  System.Collections.Generic;
using  System.Linq;
using  System.Text;

using  System.Reflection;

namespace  ActiveLoad
{
    
class Program
    
{
        
//------------定義用來打印程序集中詳細狀況的函數
        static void ListAllTypes(Assembly asm)
        
{
            Console.WriteLine(
"程序集名稱:{0}", asm.FullName);
            Type[] types 
= asm.GetTypes();

            
foreach (Type  t in types)
            
{
                Console.WriteLine(
"\n類型:{0}\t名稱:{1}", t, t.Name);
            }

        }



        
static void Main(string[] args)
        
{
            Assembly asm 
= Assembly.Load("Exercise");//--------根據程序集的友好名稱動態加載程序集,ActiveLoad項目必須添加對Exercise項目的引用
            
//ListAllTypes(asm);
            asm = Assembly.Load("LINDERMAN.Entity");
            ListAllTypes(asm);

            Console.ReadLine();
        }

    }

}

複製代碼

【運行效果】 




   注意
1.ActiveLoad項目必須添加對Exercise項目的引用

     2.靜態方法Assembly.Load方法僅僅傳入了一個要加載到內存的程序集的友好名稱,所以若是想

反射Exercise.dll,則須要把Exercise.dll文件複製到ActiveLoad應用程序的"Bin"Debug目錄,

而後再來運行這個程序。【此時仍需先在項目中手動添加對Exercise.dll的引用:

添加引用->瀏覽->ActiveLoad項目的bin文件夾..添加】

        3.若是但願讓ActiveLoad更加靈活,可使用Assembly.LoadFrom方法。此時只要在想查看的程序

集前面加上一個絕對路徑。


 

2.晚期綁定

晚期綁定是一種建立一個給定類型的實例並在運行時調用其成員而不須要在編譯時知道它存在的一種技術,對於程序的可擴展性來講很是重要。

要介紹晚期綁定技術,首先必須介紹一下System.Activator類。

System.Activator類除了繼承自Object的方法外,其自己只定義了幾個成員方法,並且其中大多數都與.net遠程處理有關

爲了創建一個晚期綁定類型的實例,當前咱們只需關注Activator.CreateInstance()方法。

CreateInstance()方法經歷過屢次重載,其中最簡單的變化是帶有一個有效的Type對象,描述但願動態分配的實體。

該方法將返回一個基本的Object類型而不是一個強類型——並非咱們所傳入的類型。

在Exercise.person類內加入打印我的信息的函數

public void DisplayInfo()

{

      Console.WriteLine("姓名:{0},年齡:{1}",this.Name,this.Age);

}

下面新建一個LataBinding項目,經過晚期綁定來創建一個Exercise.person類的實例,並調用DisplayInfo()

【源代碼】

class Program

    {

        static void Main(string[] args)

        {

            Assembly asm = Assembly.Load("Exercise");

 

            Type man = asm.GetType("Exercise.person");

 

            object Man = Activator.CreateInstance(man);//-------返回一個object類型而不是Exercise.person類型

           

        }

}

獲得的實例Man是一個Object類型而不是一個Exercise.person類型。且不能經過顯示轉換來解決問題,由於程序並不知道Exercise.person是什麼

注意:晚期綁定的重點是創建編譯時未知的對象的實例

 

下面經過反射來實現打印屬性的工做:

                            首先,使用Type.GetMethod方法

       修改源代碼以下:

 

複製代碼
class  Program
    
{
        
static void Main(string[] args)
        
{
            Assembly asm 
= Assembly.Load("Exercise");
            Type man 
= asm.GetType("Exercise.Person");//--------注意大小寫
            
            
object Man = Activator.CreateInstance(man);//-------返回一個object類型而不是Exercise.person類型
            MethodInfo mi = man.GetMethod("DisplayInfo");
            mi.Invoke(Man, 
null);

            Console.ReadLine();

        }

    }
複製代碼

其中Invoke方法含有兩個參數,第一爲Object類型,表示調用方法所依賴的實例對象;第二個參數爲數組類型,表示調用方法或構造函數所使用的參數,當賦值爲NULL時表示調用無參方法。

運行效果



下面再演示一個調用有參數方法的實例: 

調用Exercise.person的ChangeNameAndAddAge方法

 

 

複製代碼
using  System;
using  System.Collections.Generic;
using  System.Linq;
using  System.Text;

using  System.Reflection;


namespace  LateBinding
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            Assembly asm 
= Assembly.Load("Exercise");
            Type man 
= asm.GetType("Exercise.Person");//--------注意大小寫
            object Man = Activator.CreateInstance(man);//-------返回一個object類型而不是Exercise.person類型

            MethodInfo  mi 
= man.GetMethod("DisplayInfo");
            mi.Invoke(Man, 
null);
            
            mi 
= man.GetMethod("ChangeNameAndAddAge");
            
object[] par = new object[2];
            par[
0= "林田惠更名了";
            par[
1]=10;
            mi.Invoke(Man, par);
//--------執行了更名並增加歲數的方法
           
            mi 
= man.GetMethod("DisplayInfo");
            mi.Invoke(Man, 
null);//-----更名後再顯示我的信息
           
            Console.ReadLine();

        }

    }

}

複製代碼

【運行效果】
 

 

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

3.特性編程

特性就是用於類型(好比類、接口、結構)、成員(好比屬性、方法)、程序集或模塊的代碼註解。

.NET程序員可使用特性把更多的元數據嵌入到程序集中,用來修飾類型的行爲。

當在代碼中應用特性時,若是它們沒有被另外一個軟件顯式的反射,那麼嵌入的元數據基本沒什麼做用。反之,嵌入程序集中的元數據介紹將被忽略不計,而並沒有害處。

 

特性的使用者:1..NET Framework SDK中的許多工具都須要查找各類特性,C#編譯器自己就要在編譯週期中尋找各類特性是否存在。

                       2.除了開發工具,在.NET基類庫中的許多方法也被設定爲要反射指定的特性

                       3..NET CLR也巡查某些特性是否存在

                       4.用戶能夠構建反射自定義的特性和.NET基類庫中的特性的應用程序。

 

 

C#預約義特性的簡單介紹

               [CLSCompliant] 強制被註解項聽從CLS

               [DllImport] 容許.NET代碼調用任意非託管的CC++基類庫,包括操做系統中的API

               [Obsolete] 標記一個不用的類或成員

               [Serializable] 標記一個類或結構能夠被「序列化」

         [NonSerialized] 指定類或結構中的某個字段不能在序列化過程當中被持久化

               [WebMethod] 標記一個方法能夠經過HTTP請求調用,而且通知CLR將方法的返回值序列化爲XML

 

注意:1.一個特性只能被應用在緊接下來的對象,例如

                            [Serializable]

                public class Person

                {

        [NonSerialized]

        public float salary;

 

        public bool sex;

        public string[] hobbit;

    }

在該類中不能被序列化的僅是salary,而因爲實體類中註釋有[Serializable],因此其餘字段均可以被序列化

 

              2.一個項能夠被加上多種特性,以下:

                      [Serializable,

    Obsolete("這個類已通過時了,請用更新的版本")]

    public class Person

    {

        public float salary;

       ……

}

 

或者這樣

[Serializable]

    [Obsolete("這個類已通過時了,請用更新的版本")]

    public class Person

    {

        public float salary;

       ……

}

        3.能夠爲特性指定構造參數

當給特性提供構造參數時,直到該特性被其餘類型或外部工具反射後,特性才被分配大內存中,定義在特性級的字符串數據只是做爲元數據介紹被存儲在程序集中。

也就是說,直到被其餘代理反射,特性才發揮使用。

 

4.C#特性的簡化符號

             當名稱轉換時,全部.NET特性,包括本身創建的自定義特性都將加上一個Attribute得後綴,可是爲簡化使用過程,C#語言不須要輸入Attribute後綴。

       也即,       [SerializableAttribute]

                public class Person

                {

                    ……

             [Serializable]

                public class Person

                {

                    ……

            效果是同樣的。

 

 

 

自定義特性

       1)構建自定義特性

              構建自定義特性的第一步是創建一個新的派生自System.Attribute的類,考慮到安全緣由,把全部自定義特性都設計成密封的是一個好習慣

              Person類設計一個特性類以下

                     public sealed class personAttribute : System.Attribute

    {

        private string _PersonData;//---定義一個私有字段

        public personAttribute()

        {

        }

        public personAttribute(string Data)//---帶參數構造函數

        {

 

        }

 

        public String PersonData//---維護私有字段的屬性

        {

            get { return _PersonData; }

            set { _PersonData = value; }

        }

 }

                 

2)使用自定義特性

           使用自定義特性有兩種方法

           1.位置參數:使用自定義特性類的構造函數來爲其所維護的私有字段賦值

                            如:   [Exercise.Program.personAttribute("這是在使用位置參數經過構造函數賦值")]

    public class Person :BasePerson, IEnumerable

    {

        private string name = "林田惠"; //---姓名

        public int age=20;     //---年齡

 

2.命名參數:使用自定義特性的屬性成員來爲其所維護的私有字段賦值

如:   [Exercise.Program.personAttribute(PersonData="這是在使用位置參數經過構造函數賦值")]

       public class Person :BasePerson, IEnumerable

       {

           private string name = "林田惠"; //---姓名

public int age=20;     //---年齡

 

 

限制特性的使用

                              默認狀況下自定義特性能夠被應用在代碼中幾乎全部的方面(方法、類、屬性等)

                              有時但願限定自定義特性的應用範圍,則須要在自定義特性的定義中應用[AttributeUsage]特性[AttributeUsage]特性支持任意AttributeTargets枚舉值的組合

                              例如,咱們但願將剛剛定義的特性變成只可應用於類或結構,不能重複應用於一個項,且不能被應用於繼承項,則可以下修改定義:

             [AttributeUsage(AttributeTargets.Class|AttributeTargets.Struct,

                AllowMultiple=false,Inherited=false)]

        public sealed class personAttribute : System.Attribute

        {

            private string _PersonData;//---定義一個私有字段

 

            public personAttribute()

            

實例運用 

實例一 使用早期綁定反射特性

    若是但願使用早期綁定,則相應的特性須要客戶應用程序在編譯時定義

    在Exercise命名空間下定義一個屬性,將其設爲可重複應用於同一個項上,代碼以下

    namespace Exercise

{

 

        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct,

               AllowMultiple = true, Inherited = false)]

        public sealed class personDataAttribute : System.Attribute

        {

            private string _PersonData;//---定義一個私有字段

 

            public personDataAttribute()

            {

            }

 

            public personDataAttribute(string Data)//---帶參數構造函數

            {

                    _PersonData = Data;

            }

 

            public String PersonData//---維護私有字段的屬性

            {

                get { return _PersonData; }

                set { _PersonData = value; }

            }

        }

……

 

在Person類的定義上應用三個personDataAttribute屬性以下:

            [personData("這是第一個personData特性")]

    [personData("這是第二個personData特性")]

    [personData(PersonData = "這是第三個personData特性")]

    public class Person :BasePerson, IEnumerable

    {

        private string name = "林田惠"; //---姓名

        public int age=20;     //---年齡

 

        Array children=null;//---子女數組

 

        public Person()

        {

           ……

設置Exercise項目爲啓動項,在Exercise命名空間下,修改Main函數以下

public static void Main(string[] args)

        {

 

            /*      演示使用早期綁定反射特性           */

            Type t = typeof(Person);

            Object[] customAtts = t.GetCustomAttributes(false);//---false表明不搜索繼承鏈

 

            foreach (personDataAttribute pda in customAtts)

            {

                Console.WriteLine("應用了一個personDataAttribute特性,其中personData={0}",pda.PersonData);  

            }

            

            Console.ReadLine();  

}

 

運行效果以下:

       

       

 

幾點說明:1.Type.GetCustmeAttributes()方法返回一個對象數組,表示了應用到Type表明的成員上的全部特性,包含一個bool參數,指示是否擴展搜索到繼承鏈。

           2.該例子中之因此可以使用personDataAttribute特性來對Person類進行描述,是由於在Exercise程序集中personDataAttribute類被定義爲公共成員。

          3.一個困惑:我在給Person類添加屬性時是按照一、二、3的順序,可代碼運行顯示的順序倒是二、一、3.。。不知道爲何,還等高手來解答。。。


實例二 使用晚期綁定反射特性

     1.先爲Exercise程序集生成dll文件(我用的是VS2008):在解決方案資源管理器中右擊Exercise項目->屬性->應用程序->輸出類型->類庫

       2.Exercise.dll文件複製到LateBinding項目的Bin"Debug文件夾下

        3. 將LateBinding項目的Main函數修改以下:

                static void Main(string[] args)

        {

          

 

            /*         如下代碼演示晚期綁定反射特性         */

          

            //----加載程序集

            Assembly asmb = Assembly.Load("Exercise");

           

            //----獲得Exercise.personDataAttribute的類型信息

            Type PersonAttr = asmb.GetType("Exercise.personDataAttribute");

           

            //----獲得personData屬性的類型信息

            PropertyInfo proPersonData = PersonAttr.GetProperty("PersonData");

 

            //----獲得程序集中的全部類型

            Type []types = asmb.GetTypes();

 

            foreach (Type t in types)

            {

                object[] cusAttrs = t.GetCustomAttributes(PersonAttr,false);

                foreach (object o in cusAttrs)

                {

                    Console.WriteLine("特性名稱:{0}"t特性值:{1}"n", t.Name, proPersonData.GetValue(o,null));

                   

                }

 

            }

            Console.ReadLine();

 

        }

 

運行效果:



 

說明:一個方法:PropertyInfo.GetValue   用索引化屬性的可選索引值返回該屬性的值。

 public virtual Object GetValue(

         Object obj,

         Object[] index

)

參數

obj

類型:System.Object

將返回其屬性值的對象。

index

類型:System.Object[]

索引化屬性的可選索引值。對於非索引化屬性,此值應爲 null 引用。

返回值

類型:System.Object

obj 參數的屬性值。

若要使用 GetValue 方法,請先獲取類 Type。從 Type 獲取 PropertyInfo。從 PropertyInfo 使用 GetValue 方法

-----------------♧------------------------------------♧------------------------------------♧-------------------對於C#反射的基礎知識,本文由兩點未涉及:1.反射共享程序集   2.程序集級別和模塊級別特性。

相關文章
相關標籤/搜索