C#反射機制總結

注:所有的內容都來自互聯網,感謝作者的無私貢獻。

 

1.什麼是反射
Reflection,中文翻譯爲 反射
     這是.Net中獲取 運行時類型信息的方式,.Net的應用程序由幾個部分'程序集(Assembly)'模塊(Module)'類型(class)組成,而反射提供一種編程的方式,讓程序員可以在程序運行期獲得這幾個組成部分的相關信息,例如:
     通常程序員面試題,有這樣關於反射的解釋:反射可以動態地創建類型的實例,還可以將類型綁定到現有對象,或從現有對象中獲取類型。然後,可以調用類型的方法或訪問其字段和屬性。 
     Assembly類可以獲得正在運行的裝配件信息,也可以動態的加載裝配件,以及在裝配件中查找類型信息,並創建該類型的實例。
Type類可以獲得對象的類型信息,此信息包含對象的所有要素:方法、構造器、屬性等等,通過Type類可以得到這些要素的信息,並且調用之。
MethodInfo包含方法的信息,通過這個類可以得到方法的名稱、參數、返回值等,並且可以調用之。
    諸如此類,還有FieldInfoPropertyInfoConstructorInfoEventInfo等等,這些類都包含在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{}
 

這兩個裝配件中都有N1N2兩個命名空間,而且各聲明瞭兩個類,這樣是完全可以的,然後我們在一個應用程序中引用裝配件A,那麼在這個應用程序中,我們能看到N1下面的類爲AC1AC2N2下面的類爲AC3AC4
    接着我們去掉對A的引用,加上對B的引用,那麼我們在這個應用程序下能看到的N1下面的類變成了BC1BC2N2下面也一樣。
    如果我們同時引用這兩個裝配件,那麼N1下面我們就能看到四個類:AC1AC2BC1BC2
    到這裏,我們可以清楚一個概念了,命名空間只是說明一個類型是那個族的,比如有人是漢族、有人是回族;而裝配件表明一個類型住在哪裏,比如有人住在北京、有人住在上海;那麼北京有漢族人,也有回族人,上海有漢族人,也有回族人,這是不矛盾的。

    上面我們說了,裝配件是一個類型居住的地方,那麼在一個程序中要使用一個類,就必須告訴編譯器這個類住在哪兒,編譯器才能找到它,也就是說必須引用該裝配件。
    問題出現了:如果在編寫程序的時候,也許不確定這個類在哪裏,僅僅只是知道它的名稱,就不能使用了嗎?答案是可以,這就是反射了,就是在程序運行的時候提供該類型(class)的全地址,而去找到它。

3.運行期得到類型信息有什麼用?

有人也許疑問,既然在開發時就能夠寫好代碼,幹嘛還放到運行期去做,不光繁瑣,而且效率也受影響。
這就是個見仁見智的問題了,就跟早綁定和晚綁定一樣,應用到不同的場合。有的人反對晚綁定,理由是損耗效率,但是很多人在享受虛函數帶來的好處的時侯還沒有意識到他已經用上了晚綁定。這個問題說開去,不是三言兩語能講清楚的,所以就點到爲止了。
    我的看法是,晚綁定能夠帶來很多設計上的便利,合適的使用能夠大大提高程序的複用性和靈活性,但是任何東西都有兩面性,使用的時侯,需要再三衡量。

接着說,運行期得到類型信息到底有什麼用呢?
還是舉個例子來說明,很多軟件開發者喜歡在自己的軟件中留下一些接口,其他人可以編寫一些插件(程序集(dll等))來擴充該軟件的功能,比如我開發了一個銷售圖書的電子商務系統,其中的兩個業務
    一是:新增加購買訂單;
    二是,根據訂單的唯一標識號獲取某一訂單信息。
我希望以後可以很方便的擴展到多種數據庫管理系統中(MS SQL SERVER, Oracle等)來完成相關業務,那麼我需要聲明一個接口:

/// <summary>

/// Interface for the Order DAL

/// </summary>

public interface IOrder

{

    // Property 表示該系統業務採用什麼類型的數據,MS SQL or Oracle 等等
    string DataBaseType

    {

        get;

    }

    /// <summary>

    /// Method to insert an order header

    /// </summary>

    /// <param name="order">Business entity representing the order</param>

    /// <returns>OrderId</returns>

    void Insert(OrderInfo order);

    /// <summary>

    /// Reads the order information for a given orderId

    /// </summary>

    /// <param name="orderId">Unique identifier for an order</param>

    /// <returns>Business entity representing the order</returns>

    OrderInfo GetOrder(int orderId);

}

實體類(OrderInfo

    /**//// <summary>

    /// Business entity used to model an order

    /// </summary>

    public class OrderInfo

    {

        private int orderId;

        private DateTime date;

        private string userId;

        private string orderNo;

        private string courierWay;

        private string paymentWay;

        /**//// <summary>

        ///  訂單ID
        /// </summary>

        public int OrderId

        {

            get { return orderId; }

            set { orderId = value; }

        }

        /**//// <summary>

        /// 訂單提交日期
        /// </summary>

        public DateTime Date

        {

            get { return date; }

            set { date = value; }

        }

        /**//// <summary>

        /// 用戶ID

        /// </summary>

        public string UserId

        {

            get { return userId; }

            set { userId = value; }

        }

        /**//// <summary>

        /// 訂單編號

        /// </summary>

        public string OrderNo

        {

            get { return orderNo; }

            set { orderNo = value; }

        }

        /**//// <summary>

        /// 配送方式

        /// </summary>

        public string CourierWay

        {

            get { return courierWay; }

            set { courierWay = value; }

        }

        /**//// <summary>

        /// 支付方式

        /// </summary>

        public string PaymentWay

        {

            get { return paymentWay; }

            set { paymentWay = value; }

        }

   }

這個接口中包含:
1)一個DataBaseType屬性,這個屬性返回擴展的或是要支持的數據庫管理系統的標識或描述,這樣就可以知道是使用那個數據庫系統,根據Web.config提取相關了(不明白就往下走);
2)第一個方法(InsertOrderInfo order))(這裏我定義了一個OrderInfo(實體),這個類(實體對象)提供對數據的封裝,在這裏,簡單地說就是將訂單的相關信息(見實體類中定義的屬性),封裝在該實體類創建的實體對象中,作爲該插入(Insert)新紀錄的參數。
3)第二個方法(GetOrderint orderId)) 這個方法由提供的訂單IdorderId)從數據庫中,讀取相關數據,記錄在創建的OrderInfo實體對象中,通過該實體對象的屬性可以讀取相關信息,如訂單生成日期,金額,發送地址等;

      那麼我規定所有的數據訪問邏輯(基於基於某一數據庫系統)都必須派生一個數據庫訪問層,並且實現這個接口,在定義的方法中返回處理處理Code對象,並且可以指定,數據庫類型的描述(如DBMS Name)。
      這樣的話,我就不需要在開發系統時知道將來需要採用的數據系統類型(是採用Ms SqlServer 呢?還是採用Oracle呢?等等),只需要從配置文件中獲取現在所指定的數據庫系統的描述,就可以動態的創建對象,將其轉換爲IOrder接口來使用。

4.通過一個Demo演示,一個對象的實例泄漏的祕密(這是我從別人那看來的)

class Program

{

    static void Main(string[] args)

{

classA a=new classA();

TestObjectType test =new TestObjectType();

Test.FucType(a);

    }

}

class classA

{

    internal int iNumberA = 100;

    public int iNumberB = 200;

    private int property;

    public int Property

    {

        get

        {

            return property;

        }

        set

        {

            property = value;

        }

    }

    public void FunA()

    {

        Console.WriteLine("classA is a Fuction! ");

    }

}

class classB

{

}

class TestObjectType

{

    internal void FucType(object A)

    {

        Type objType = A.GetType();

        Assembly objassembly = objType.Assembly;

        Type[] types = objassembly.GetTypes();

        foreach (Type type in types)

        {

            Console.WriteLine("類名 " + type.FullName);

            // 獲取類型的結構信息
            ConstructorInfo[] myConstructor = type.GetConstructors();

            Show(myConstructor);

            // 獲取類型的字段信息
            FieldInfo[] myField = type.GetFields();

            Show(myField);

            // 獲取方法的方法
            MethodInfo[] myMethod = type.GetMethods();

            Show(myMethod);

            // 獲取屬性的方法
            PropertyInfo[] myProperty = type.GetProperties();

            Show(myProperty);

            // 獲取事件信息,這個Demo沒有事件,所以就不寫了 EventInfo
        }

        Console.ReadLine();

    }

     // 顯示數組的基本信息
    private void Show(object[] myObject)

    {

        foreach (object var in myObject)

        {

            Console.WriteLine(var.ToString());

        }

        Console.WriteLine("-------------------");

    }

}

運行結果如下圖所示:

但是,測試分析後,發現其實也只能獲得public 類型的信息

 5.動態創建對象實例的基礎
是實現抽象工廠的基礎,也是實現抽象工廠的核心技術,通過它,可以動態創建一個你想要的對象.
如下面的例子是演示如何動態創建ChineseNameEnglishName的實例。

這個Demo3到的Demo如出一轍,這個更簡化些(用C#控制檯應用程序 編寫的),並提供完整的動態創建對象的代碼,讀者對照兩者揣摩加聯繫。其實都很簡單。
using System;

using System.Collections.Generic;

using System.Text;

using System.Reflection;

namespace TestReflection

{

    class Program

    {

        static void Main(string[] args)

        {

            IName objChineseName = AbstractFactory.createChineseName();

            objChineseName.ShowName();

            IName objEnglishName = AbstractFactory.createChineseName();

            objEnglishName.ShowName();

        }

    }

    //  聲明一個接口,它有一個顯示"名字"的功能(ShowName方法)
    public interface IName

    {

        void ShowName();

    }

    // 實現接口,顯示中國名字
    public class ChineseName : IName

    {

        #region 成員函數

        public void ShowName()

        {

            Console.WriteLine("我叫XX!");

            Console.ReadLine();

        }

        #endregion

    }

    // 實現接口,顯示中國名字
    public class EnglishName : IName

    {

        #region 成員函數

        public void ShowName()

        {

            Console.WriteLine("My name is XX a!");

            Console.ReadLine();

        }

        #endregion

    }

    // 最爲重要的代碼段,往下看
    // 使用抽象工廠的方法來進行動態創建對象實例應用哦

    public sealed class AbstractFactory

    {

        public static readonly string path = "TestReflection";

        public static IName createChineseName()

        {

            // 的值以後從Web.Config動態讀取,如下所示
            /* 

<appSettings>
         <add key="WebDAL" value="[類的全路徑]"/>
    <appSettings> 

            */

            // className賦值爲:TestReflection.ChineseName,將顯示中文名
            string className = path + ".ChineseName";

            return (IName)Assembly.Load(path).CreateInstance(className);

        }

        public static IName createEnglishName()

        {

            string className = path + ".EnglishName";

            return (IName)Assembly.Load(path).CreateInstance(className);

        }

    }

}

6.獲得整個解決方案的所有Assembly
如果你不太清楚自己的解決方案中都用到了哪些Assembly,可以使用下面的方法,如果再想得到Assembly裏的信息,請參見 4 

namespace TestReflection

{

    class Program

    {

        static void Main(string[] args)

        {

            // 遍歷顯示每個Assembly的名字
            foreach (object var in Ax)

            {

                Console.WriteLine("Assembly的名字是 " + var.ToString());

                

            // 使用一個已知的Assembly的名稱,來創建一個Assembly
                // 通過CodeBase屬性顯示最初指定的程序集的位置
                Console.WriteLine("最初指定的程序集TestReflection的位置 " + Assembly.Load("TestReflection").CodeBase);

                Console.ReadLine();

            }

        }

    }

}

【補充:】

1、如何使用反射獲取類型
    首先我們來看如何獲得類型信息。
    獲得類型信息有兩種方法,一種是得到實例對象
    這個時侯我僅僅是得到這個實例對象,得到的方式也許是一個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");
     原因如下:

Type.GetType屬於Reflection的一部分,Reflection可以用於動態解析類型。也就是說,即使編譯開發期間沒有建立對Assembly的引用,Type.GetType一樣可以用於取得類型信息(相反,typeof是靜態解析類型的,所以要求reference)

如果你理解NamespaceAssembly的關係的話,就會明白單從類型的名稱(包括Namespace部分)是不可能找到類型定義的,還必須知道定義類型的Assembly的名稱和位置。所以在調用Type.GetType的時候必須要附帶指明Assembly的信息。

> >   但我想不通了   爲什麼他們的實例GetType就可以呢???
Object.GetType的工作方式和上面的不同。因爲你已經得到了對象實例(object),也就意味着在這之前你已經通過某種方式把定義對象的Assembly加載搗內存裏了,不需要額外的信息來確定定義類型的Assembly

如果是當前的Assembly或者默認的Assembly(mscorlib)GetType的時候是不需要指定Assembly的,除此之外,都必須指定Assembly的名字,也就是Type的屬性AssemblyQualifiedName所代表的值。

對於String
Type t = 

  Type.GetType("System.String,mscorlib,Version=1.0.5000.0,Culture=neutral,   PublicKeyToken=b77a5c561934e089 ");
因爲屬於mscorlib,所以也可以:t = Type.GetType( "System.String ");
SqlDataAdapter必須指定Assembly名:
t=Type.GetType("System.Data.SqlClient.SqlDataAdapter,System.Data,Version=1.0.5000.0,   Culture=neutral,   PublicKeyToken=b77a5c561934e089 ");


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

3、如何獲取方法以及動態調用方法
namespace TestSpace

{

    public class TestClass

    {

        private string _value;

        #region Constructors

        public TestClass()

        { }     

        

        public TestClass(string value) 

        { 

            _value = value;

        }

        #endregion

        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

3、動態創建委託
   委託是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);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章