反射原理及簡介

一.什麼是反射程序員

 Reflection,中文翻譯爲反射。這是.Net中獲取運行時類型信息的方式,編程

.Net的應用程序由幾個部分:‘程序集(Assembly)’、‘模塊(Module)’、‘類型(class)’組成,而反射提供一種編程的方式,讓程序員能夠在程序運行期得到這幾個組成部分的相關信息,api

例如:Assembly類能夠得到正在運行的程序集信息,也能夠動態的加載程序集,以及在程序集中查找類型信息,並建立該類型的實例。數組

Type類能夠得到對象的類型信息,此信息包含對象的全部要素:方法、構造器、屬性等等,經過Type類能夠獲得這些要素的信息,而且調用之。ui

MethodInfo包含方法的信息,經過這個類能夠獲得方法的名稱、參數、返回值等,而且能夠調用之。諸如此類,this

還有FieldInfo、EventInfo等等,這些類都包含在System.Reflection命名空間下。spa

 二. 關於程序集和命名空間的關係翻譯

不少人對這個概念可能仍是很不清晰,對於合格的.Net程序員,有必要對這點進行澄清。設計

程序集是.NET應用程序執行的最小單元,編譯出來的.dll和.exe都是程序集。 程序集和命名空間的關係不是一一對應,也不互相包含,一個程序集裏面能夠有多個命名空間,一個命名空間也能夠在多個程序中存在,這樣說可能有點模糊,舉個例子:code

程序集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下面咱們就能看到四個類:AC一、AC二、BC1和BC2。

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

上面咱們說了,程序集是一個類型居住的地方,那麼在一個程序中要使用一個類,就必須告訴編譯器這個類住在哪兒,編譯器才能找到它,也就是說必須引用該程序集。

 那麼若是在編寫程序的時候,也許不肯定這個類在哪裏,僅僅只是知道它的名稱,就不能使用了嗎?答案是能夠,這就是反射了,就是在程序運行的時候提供該類型的地址,而去找到它。

 

 三.爲何使用反射

 有人會有疑問,程序所用的類既然能夠事先寫好,那麼爲何還要在程序運行的時候去生成,這樣豈不是浪費系統資源。存在就是合理的,既然微軟給咱們開發這項技術,確定是這個這個東西有需求,舉個簡單的例子:

如今要開發一個報表打印模塊,有的企業要求數據以Excel報表,有的企業要求打印水晶報表等等,這個時候咱們就能夠先定義一個接口,任何報表打印方法都必須實現這個接口:

 

   public interface  IReport
    {
        void StartPrint();
    }

咱們經過配置文件能夠加載對應的讀取報表的類型,由於報表打印類都實現了IReport的接口,因此均可以經過反射的方式強轉爲該接口類型,這樣就能夠實現無需修改底層代碼就能夠實現打印不一樣的數據報表,符合我i們程序設計的開閉原則,這個就是反射最經典的應用。

public static class Factory
{
//【1】讀取配置文件
static string reportType = ConfigurationManager.AppSettings["ReportType"].ToString();
//【2】使用反射建立實現接口類的對象並以接口類型返回
public static IReport ChooseReportType()
{
return (IReport)Assembly.Load("UseFactory").CreateInstance("UseFactory." + reportType);
}
}

四.如何使用反射獲取類型

獲取類信息有兩種方式:

第一種方式,獲得實例化對象。 這個時侯我僅僅是獲得這個實例對象,獲得的方式也許是一個object的引用,也許是一個接口的引用,可是我並不知道它的確切類型,我須要瞭解,那麼就能夠經過調用System.Object上聲明的方法GetType來獲取實例對象的類型對象,好比在某個方法內,我須要判斷傳遞進來的參數是否實現了某個接口,若是實現了,則調用該接口的一個方法。

   public void Progress(object o)
        {
            Type objType = o.GetType();

            if (objType.GetInterface("ITest") !=null)
            {
                //調用該接口的方法
            }
        }

第二種獲取類型的方法是經過Type.GetType以及Assembly.GetType方法,可是在使用該方法時咱們須要注意一些問題點。在程序集A.dll中須要反射程序集B.dll中的類型。若是使用稍有不慎,就會產生運行時錯誤。例如使用Type.GetType("BNameSpace.ClassName")在程序集A.dll獲取程序集B.dll中的類型,就會返回Null。

關於跨程序集的反射,有兩點須要注意:

一、若是使用typeof,編譯能經過,則跨程序集的反射必定能夠正常運行。能夠說,typeof是支持強類型的。好比

 Type supType = typeof(BNameSpace.SubSpace.Class);

若是當前程序集沒有添加對EnterpriseServerBase.dll的引用,則編譯會報錯。

二、若是使用Type.GetType來進行反射的話,狀況就複雜些。這是由於Type.GetType是非強類型的。Type.GetType的參數是一個string爲類型的徹底限定名,若是當string表示的目標類型不在當前程序集中,則運行時Type.GetType會返回null。解決的辦法是:首先加載目標程序集,而後再使用Assembly.GetType方法來獲取類型。如:

Assembly asmb = Assembly.LoadFrom("EnterpriseServerBase.dll") ;
Type supType = asmb.GetType("EnterpriseServerBase.DataAccess.IDBAccesser") ;

注意:當使用Type.GetType的時候,即便你添加了對EnterpriseServerBase.dll的引用,Type.GetType("EnterpriseServerBase.DataAccess.IDBAccesser")也會返回null,這是由於Type.GetType只會在當前程序集中進行類型搜索。

5.如何根據類型動態建立對象

第一種方式

public class Example
{
    static void Main()
    {
        // Create an instance of the StringBuilder type using 
        // Activator.CreateInstance.
        Object o = Activator.CreateInstance(typeof(StringBuilder));

        // Append a string into the StringBuilder object and display the 
        // StringBuilder.
        StringBuilder sb = (StringBuilder) o;
        sb.Append("Hello, there.");
        Console.WriteLine(sb);

        // Create an instance of the SomeType class that is defined in this 
        // assembly.
        System.Runtime.Remoting.ObjectHandle oh = 
            Activator.CreateInstanceFrom(Assembly.GetEntryAssembly().CodeBase, 
                                         typeof(SomeType).FullName);

        // Call an instance method defined by the SomeType type using this object.
        SomeType st = (SomeType) oh.Unwrap();

        st.DoSomething(5);
    }
}

 

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

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數組中便可

第三種方式:

假如咱們並無加載對應.cs文件的程序集,那麼咱們該如何簡潔的建立對象類型呢:

    object objCal =Assembly.LoadFrom("CalDLL.dll").CreateInstance("CalDLL.Calculator");

是否是很簡潔,固然咱們也能夠先獲取對象的類型,再用Activator.CreateInstance 來建立對象,不過稍顯麻煩:

 Assembly objAssembly = Assembly.LoadFrom("CalDLL.dll");


 Type objType = objAssembly.GetType("CalDLL.Calculator");


 object objCal = Activator.CreateInstance(objType);

這樣也能達到相同的效果。

 

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

應用反射動態調用方法的示例以下:

using System;
using System.Reflection;

class Program
{
    // Methods to get:

    public void MethodA(int i, int j) { }

    public void MethodA(int[] i) { }

    public unsafe void MethodA(int* i) { }

    public void MethodA(ref int r) {}

    // Method that takes an out parameter:
    public void MethodA(int i, out int o) { o = 100;}


  static void Main(string[] args)
  {
    MethodInfo mInfo;

    // Get MethodA(int i, int j)
    mInfo = typeof(Program).GetMethod("MethodA",
        BindingFlags.Public | BindingFlags.Instance,
        null,
        CallingConventions.Any,
        new Type[] { typeof(int), typeof(int) },
        null);
    Console.WriteLine("Found method: {0}", mInfo);

    // Get MethodA(int[] i)
    mInfo = typeof(Program).GetMethod("MethodA",
        BindingFlags.Public | BindingFlags.Instance,
        null,
        CallingConventions.Any,
        new Type[] { typeof(int[]) },
        null);
    Console.WriteLine("Found method: {0}", mInfo);

    // Get MethodA(int* i)
    mInfo = typeof(Program).GetMethod("MethodA",
        BindingFlags.Public | BindingFlags.Instance,
        null,
        CallingConventions.Any,
        new Type[] { typeof(int).MakePointerType() },
        null);
    Console.WriteLine("Found method: {0}", mInfo);

    // Get MethodA(ref int r)
    mInfo = typeof(Program).GetMethod("MethodA",
        BindingFlags.Public | BindingFlags.Instance,
        null,
        CallingConventions.Any,
        new Type[] { typeof(int).MakeByRefType() },
        null);
    Console.WriteLine("Found method: {0}", mInfo);

    // Get MethodA(int i, out int o)
    mInfo = typeof(Program).GetMethod("MethodA",
        BindingFlags.Public | BindingFlags.Instance,
        null,
        CallingConventions.Any,
        new Type[] { typeof(int), typeof(int).MakeByRefType() },
        null);
    Console.WriteLine("Found method: {0}", mInfo);

  }
}
關於GetMethod()的更多信息能夠經過以下:

https://docs.microsoft.com/en-us/dotnet/api/system.type.getmethod?view=netframework-4.8

7.如何動態的獲取屬性信息:

下面的例子簡單的說明了如何獲取屬性信息

 void GetProperty()
        {
            Type objType = typeof(string);
           PropertyInfo[]  obj =  objType.GetProperties();
           if (obj !=null)
           {
               foreach (var item in obj)
               {
                   this.textBox1.Text = this.textBox1.Text + item.Name.ToString() + "\r\n";
               }
           }
        }
相關文章
相關標籤/搜索