-
概述 什么是反射
Reflection,中文翻譯為反射。
這是.Net中獲取運行時類型信息的方式,.Net的應用程序由幾個部分:‘程序集(Assembly)’、‘模塊(Module)’、‘類型(class)’組成,而反射提供一種編程的方式,讓程序員可以在程序運行期獲得這幾個組成部分的相關信息,例如:
Assembly類可以獲得正在運行的裝配件信息,也可以動態的加載裝配件,以及在裝配件中查找類型信息,並創建該類型的實例。
Type類可以獲得對象的類型信息,此信息包含對象的所有要素:方法、構造器、屬性等等,通過Type類可以得到這些要素的信息,並且調用之。
MethodInfo包含方法的信息,通過這個類可以得到方法的名稱、參數、返回值等,並且可以調用之。
諸如此類,還有FieldInfo、EventInfo等等,這些類都包含在System.Reflection命名空間下。
類型 | 作用 |
Assembly | 通過此類可以加載操縱一個程序集,並獲取程序集內部信息 |
EventInfo | 該類保存給定的事件信息 |
FieldInfo | 該類保存給定的字段信息 |
MethodInfo | 該類保存給定的方法信息 |
MemberInfo | 該類是一個基類,它定義了EventInfo、FieldInfo、MethodInfo、PropertyInfo的多個公用行為 |
Module | 該類可以使你能訪問多個程序集中的給定模塊 |
ParameterInfo | 該類保存給定的參數信息 |
PropertyInfo | 該類保存給定的屬性信息 |
這些都是廢話,我們一起看幾個案列就完全學會了,在此說明下,反射用到的一些基礎技術有 運行運算符,type 類,這里就不過多的解釋了,如有不會可以去園子里面自己去找,本人也寫過一篇相關文章,簡單的介紹了運行運算符。
-
如何得到一個類的對象
現有工程文件(項目文件)結構如下
People類代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Entity { public class People { public People() { Console.WriteLine("People被創建了"); } public People(String Name) { this.Name = Name; Console.WriteLine("People被創建了,並且people的名字是"+this.Name); } public string Name { get; set; }//自動屬性,在程序實例化的過程中會自動創建私有的字段,這個字段在people 內存中開辟控件存儲其值(本文稱為公有屬性)在此感謝ENC博主的支持和評論, public int Age { get; set; } public string Sex { get; set; } public string msg;//公有字段 private string qq;//私有字段 private string address;//私有屬性 public string Address { get => Address; set => Address = value; } public override string ToString() { return "{" + $"name:{this.Name},age:{this.Age},sex{this.Sex}" + "}"; } public string Say() { return "hello! " + this.Name; } } }
debug 目錄如下:
這里說明下,程序中,並沒有引用 Entity 類庫,也沒有引用Entity..DLL文件,請自行引用,我們如果不實例化得到一個對象呢??正常的時候,我們都是通過new 得到一個對象,如:
using Entity; using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { People p = new People(); Console.WriteLine(p); People peop = new People("張三"); Console.WriteLine(p); Console.Read(); } } }
我們再來看下類的類型是什么?
using Entity; using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { Type t = typeof(People); Console.WriteLine(t); Type type= Type.GetType("People"); Console.WriteLine(type);//這里是得不到的,因為配件裝載只能在程序集內部使用 Console.Read(); } } }
我們來學習下,如何根據類類型進行反射。
-
類的反射
對象無參構造函數反射
static void Main(string[] args) { Type type = typeof(People); People people= Activator.CreateInstance(type) as People;//實例化得帶一個類 Console.WriteLine(people); Console.Read(); }
對象有構造函數參反射
static void Main(string[] args) { Type type = typeof(People); People people= Activator.CreateInstance(type) as People;//實例化得到一個類 Console.WriteLine(people); //實例化得到一個類,該類有一個參數 People p = Activator.CreateInstance(type, new object[] { "Wbcsky" }) as People; Console.WriteLine(p); Console.Read(); }
對象泛型反射
static void Main(string[] args) { Type type = typeof(People); People p1 = Activator.CreateInstance<People>(); Console.WriteLine(p1); Console.Read(); }
關於對象的反射,就只有這三種形式,分別是泛型反射,泛型反射有且只能得到無參數的實例對象,和普通無參反射像比較,反射反射減少了裝箱拆箱的操作。有參數反射我們是按照參數的順序,傳遞的object 數組。這些反射都是基於 Activator.CreateInstance 來完成的。
static void Main(string[] args) { Type type = typeof(People); System.Reflection.PropertyInfo[] p = type.GetProperties(); foreach (var item in p) { Console.WriteLine("屬性名:" + item.Name + "屬性類型" + item.PropertyType.FullName + "屬性類型命名空間" + item.PropertyType.Namespace); } Console.Read(); }
我們都知道,在C#中,屬性的封裝有兩種,一種全寫,一種簡寫,全寫的在某些工具書中叫做私有屬性,簡寫的在工具書上叫做公有屬性。
如:
public int Age { get; set; }
我們稱為簡寫,工具書上叫做公有屬性。
則:
private string address;//私有屬性
public string Address { get => Address; set => Address = value; }
或
private string iD;
public string ID
{
get { return this.iD; }
set { this.iD = value; }
}
這種寫法我們稱為私有屬性,私有屬性中,當使用=>這種運算的,我們稱為lambda表達式寫法,使用this 關鍵字的寫法,我們稱為面向對象寫法。不論哪一種屬性,我們都叫做屬性,我們在反射中獲取屬性使用的是Type 類的 .GetProperties()方法來獲取類的全部屬性。我們來看下執行結果。

這里就不過多的介紹獲取屬性的值了,我們在下面介紹獲取屬性的值。
-
獲取指定名稱的屬性和值及設置一個值
static void Main(string[] args) { Type type = typeof(People); System.Reflection.PropertyInfo Property = type.GetProperty("Name");//注意屬性名稱字符串大小寫 if (Property == null) Console.Read();//如果屬性名稱大小寫錯誤或者不存在,我們Property對象將會是null Console.WriteLine("屬性名:" + Property.Name + "屬性類型" + Property.PropertyType.FullName + "屬性類型命名空間" + Property.PropertyType.Namespace); //獲取屬性的值 People p= Activator.CreateInstance(type) as People;//獲取對象 object oName = Property.GetValue(p); //獲取值 Console.WriteLine("舊" + oName); Property.SetValue(p, "abc");//設置一個值 oName = Property.GetValue(p); //獲取值 Console.WriteLine("新" + oName); Console.Read(); }
看了上面的代碼,我們會發現,獲取屬性使用的是Type類的 GetProperty方法來完成的。獲取值和設置值,使用的是 PropertyInfo 類的 GetValue和Set value 來完成的。執行結果如下

因為初始化的時候是空,所以舊就什么也沒有輸出。有人會說了,這個沒有獲取到類,進行點寫的方便,為什么要這么寫呢,告訴你一句話,存在就是有道理的,這里可以簡單的告訴,我們很多時候,一個功能更新過於頻繁,我們完全可以把這個類寫入配置文件中,去配置這個類對象的功能使用。理解即可,不理解清背下來代碼。
-
獲取對象的所以公有字段和私有字段
在這里說明下,很多人都不明白字段和屬性的區別,我這里簡單說下,理解即可,不理解不影響學習,我們一個類的變量進行封裝,會出現get ,set 設置這個字段的訪問權限,這個封裝我們稱為屬性,而這個變量我們叫做字段,字段不指定修飾符的時候默認為私有的。
static void Main(string[] args) { Type type = typeof(People); System.Reflection.FieldInfo[] fi = type.GetFields(); Console.WriteLine("\r\n-------------------- 獲取對象的所以公有字段-------------------------------\r\n"); foreach (System.Reflection.FieldInfo item in fi) { Console.WriteLine("公有字段名" + item.Name); } Console.WriteLine("\r\n-------------------- 獲取對象的所有私有字段-------------------------------\r\n"); System.Reflection.FieldInfo[] fiprivate = type.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); foreach (System.Reflection.FieldInfo item in fiprivate) { Console.WriteLine("私有字段名" + item.Name); } Console.Read(); }
這是一個難點,但是在實際開發過程中很少使用,但是這我們必須要會,否則后期寫組件開發等文檔,該看不懂了,准備好瓜子,咱們開始聽故事了。
看了上面的代碼,及字段及屬性的介紹,我們會發現,輸出的結果,共有的很好理解,我們類里面定義的變量 指定public 以后,我們就可以通過
GetFields ()
方法返回我們想要的公有字段數組,我們輸出了名字,這里就不過多的解釋了。
反射私有字段,輸出的這個是什么啊,亂碼七招的。
私有字段名<Name>k__BackingField
私有字段名<Age>k__BackingField
私有字段名<Sex>k__BackingField
私有字段名qq
私有字段名address
其實很好理解,我們在前面說過獲取所有屬性的時候說過屬性分為私有和公有,其中私有屬性有兩種寫法,其實私有屬性是對私有變量的封裝,也可以說是對私有字段的封裝,公有屬性是什么呢?
其實公有屬性在編譯過程中, 為了方便JTL 公共語言運行環境更好的編譯,自動生成了一個私有的字段,這個字段是根據操作系統不同生成不同前綴的私有字段,這里生成的是K_前綴的。這樣我們就好理解為什么上圖會多輸出三個字段。
如果此處還不理解,那么請看其他博客吧本文介紹的畢竟都是基礎。而實際開發過程中反射這基本使用的都是組件。
- 獲取指定的公有字段
在這里就不介紹獲取指定公有字段的值了,和屬性獲取是一樣的。
static void Main(string[] args) { Type type = typeof(People); Console.WriteLine("\r\n-------------------- 獲取對象的指定公有字段-------------------------------\r\n"); Console.WriteLine("字段名" + type.GetField("msg").Name); Console.Read(); }
代碼很簡單,只有一行。那么有人會問,那字段分為私有和共有的,為啥沒有介紹獲取私有屬性的呢???為啥沒有介紹獲取指定私有字段的呢???,其實答案很簡單,你看過有封裝屬性的時候有私有的嗎,私有的是不是都說在類的內部使用,那我反射類就可以了,我外部也不使用。那私有字段呢,為啥沒有,不是沒有,是有但是基本不使用,因為共有屬性會默認生成私有字段,這個私有字段的前綴不同,所以無法獲取,沒意義。所以基本沒人使用。
-
獲取公有方法並調用
Type type = typeof(People); Console.WriteLine("\r\n-------------------- 獲取對象的共有方法並且調用-------------------------------\r\n"); System.Reflection.MethodInfo mi = type.GetMethod("Say"); People p= Activator.CreateInstance<People>(); p.Name = "張四伙";//為了省事,這里不使用屬性反射添加值了 object oReturn = mi.Invoke(p, null);//第一個參數為反射的對象,第二個參數object 數組,為參數,參數按順序填寫 Console.WriteLine(oReturn); Console.Read();
這個沒有什么解釋的了,前面最難的屬性字段反射,我們都會了,這個就不是問題了,自己多看看代碼?
-
獲取當前類下的所有夠着函數
static void Main(string[] args) { Type type = typeof(People); ///獲取所有的一般不會使用,這里就不過多介紹了 System.Reflection.ConstructorInfo[] info = type.GetConstructors();//獲取當前類下所有夠着函數 foreach (System.Reflection.ConstructorInfo item in info) { Console.WriteLine("是否為虛方法"+item.IsVirtual); Console.WriteLine("名稱"+item.Name); } Console.WriteLine("\r\n-------------------- 獲取當前類下參數類型匹配的夠着函數-------------------------------\r\n"); System.Reflection.ConstructorInfo con = type.GetConstructor(new Type[] { typeof(string) }); object o = con.Invoke(new object[] { "zhangsan" }); People peo = o as People; Console.WriteLine(peo); Console.Read(); }
大家會說了,夠着函數不就是類對象的實例化嗎?,我們前面不是講過反射類對象了嗎,為什么這個里面還要獲取實例化對象呢?
其實有些時候,我們在使用抽象類和接口的時候,我們通過之前學習的類的反射是一樣可以做到得到類的對象,這里之說以這么講解,因為有一些反射項目在優化的時候,會使用內部查找原則,即從夠着函數開始得帶類的對象,效率會更高一些。
我們在開發過程中,盡量有內而外,盡量把計算或者聲明拿到程序代碼執行過程中的最后去做,這樣使用內存會少,效率會更高。
下邊我們學習這篇文章的第二大核心。程序集反射
什么是程序集反射呢,加入我們三層架構,我不想引用bll層和model 層,也不想引用他們的dll,就能在業務層得帶他的對象引用,這個怎么做到呢???我們一起來學習下吧!
首先程序集中刪除Entity.dll 程序編譯跟目錄放置 ectity.dll文件。看下列代碼
using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { /*裝載程序集*/ System.Reflection.Assembly assembly = System.Reflection.Assembly.Load("Entity"); // System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom("Entity.bll");//使用這種方式需要寫擴展名 Console.WriteLine("\r\n-------------------- 程序集反射1-------------------------------\r\n"); Type peopleType = assembly.GetType("Entity.People");//得到people 類的type 類型 object obj = Activator.CreateInstance(peopleType); System.Reflection.MethodInfo me = peopleType.GetMethod("Say"); object ret = me.Invoke(obj, null); Console.WriteLine(ret); Console.WriteLine("\r\n-------------------- 程序集反射2-------------------------------\r\n"); object PeopleObj = assembly.CreateInstance("Entity.People");//直接得到類的實例化對象 Console.WriteLine(PeopleObj); Console.Read(); } } }
代碼注釋已經很明確了,這里就不過多的解釋了,我們來看下執行結果 。
-------------------- 程序集反射1-------------------------------
People被創建了
hello!
-------------------- 程序集反射2-------------------------------
People被創建了
{name:,age:0,sex}
在程序集反射中,我們就沒有辦法在.屬性 .字段 .方法的調用了,這個時候,我們只能通過屬性,方法的反射去調用了,這里演示的不多,就兩種常用的案列,剩下的程序集有參數,無參數夠造函數就不多說了,和前面的是一樣的,本文只是介紹了開發過程中常用的案列。
現有泛型類如下
public class GenericClass<T, W, X> { public void Show(T t, W w, X x) { Console.WriteLine("t.type={0},w.type={1},x.type={2}", t.GetType().Name, w.GetType().Name, x.GetType().Name); } }
反射代碼如下:
Assembly assembly = Assembly.Load("Entity"); Type genericType = assembly.GetType("Entity.GenericClass`3"); Type typeNew = genericType.MakeGenericType(typeof(int), typeof(int), typeof(int)); Dynamic dGeneric = Activator.CreateInstance(typeNew);
泛型反射,我們有幾個泛型參數,我們就在后邊補位“`3”,注意符號 ` 可千萬別少了,我們泛型反射是使用Type 類的 MakeGenericType()方法進行獲取泛型的Type 類型的,通過這個類型進行反射
1.反射一般是用在序列化無法完成的情況下,比如接口返回想xml,而這個xml 經常變動,並沒有一個指定的規律,這個時候我們就不能用linq to xml 等反序列化對象了。這個時候就應當使用反射了。
2.真正開發過程中,反射不是是向上面這么寫的,真正的反射是使用組件來完成的,一般也不會使用程序集反射,除非這個框架的某個功能模塊更新頻繁,我們可以使用不同的反射區完成,只需要在xml 文件中配置下就可以了。
3.在這里簡單介紹下組件反射,不是說開發過程中不會有程序集等反射,而是大多數的情況下組件反射就已經能滿足我們的需求了,如AutoFac組件,等其他的。
4.反射技術點一般對應的技術點有 IOC 翻轉,依賴倒置,依賴注入等
下邊分享一篇文章,之所以寫本文,就是因為下邊這篇文文章介紹的太主流,很多人不會使用,Autofac是net core 2.0里面的組件,請看下邊的文章
文章鏈接1 Autofac 解釋第一個例子 《第一篇》
文章鏈接2 Autofac 組件、服務、自動裝配 《第二篇》
文章鏈接3 通過配置的方式Autofac 《第三篇》
以上三篇合起來,我們稱為IOC 設計模式