C#反射機制介紹
先看下面一個動物點名系統(tǒng)的簡單例子:
有一個Animal的抽象動物父類,里面定義了Name、Age兩個屬性和一個Shout()方法,Animal類定義如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Animal { /// <summary> /// 抽象父類 /// </summary> public abstract class Animal { /// <summary> /// Name屬性 /// </summary> public string Name { get; set; } /// <summary> /// Age屬性 /// </summary> public int Age { get; set; } /// <summary> /// Shout抽象方法 /// </summary> public abstract void Shout(); } }
分別定義Cat、Dog類繼承自Animal類,Cat類定義如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Animal { public class Cat :Animal { /// <summary> /// 構造函數(shù)初始化 /// </summary> public Cat() { base.Name = "湯姆"; base.Age = 2; } public override void Shout() { Console.WriteLine("喵喵喵,我是{0},今年{1}歲", base.Name,base.Age); } } }
Dog類定義如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Animal { public class Dog : Animal { /// <summary> /// 構造函數(shù)初始化 /// </summary> public Dog() { base.Name = "布魯斯"; base.Age = 3; } public override void Shout() { Console.WriteLine("汪汪汪,我是{0},今年{1}歲", base.Name, base.Age); } } }
應用場景:在一個控制臺程序中,輸入具體的動物的類型,根據(jù)輸入的動物類型,輸出Name、Age和Shout()方法,使用傳統(tǒng)方式實現(xiàn)的代碼如下:
using Animal; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Reflection; namespace ReflectionCon { class Program { static void Main(string[] args) { Console.WriteLine("請錄入動物類型:"); string type = Console.ReadLine().Trim(); Animal.Animal a = null; switch (type) { case "cat": a = new Cat(); a.Shout(); break; case "dog": a = new Cat(); a.Shout(); break; } Console.ReadKey(); } } }
程序運行結果如下:
那么問題來了:如果我們想要增加一個動物類型,那么就需要修改現(xiàn)有的代碼,在switch里面增加判斷。但是這種方式很不利于以后的維護,違反了開閉原則,每次增加一個動物類型的時候,都需要修改代碼。那么有沒有其他方式可以做到不用修改代碼就可以實現(xiàn)呢?答案是肯定的,那就是使用我們接下來要講的反射,先來了解一下什么是反射。
一、什么是反射
在講解什么是反射之前,先來了解應用程序的結構。
程序代碼在編譯后生成可執(zhí)行的應用,我們首先要了解這種可執(zhí)行應用程序的結構。
應用程序結果分為應用程序域-程序集-模塊-類型-成員幾個層次,公共語言運行時(CLR)加載器管理應用程序域,這種管理包括將每個程序集加載到相應的應用程序域以及控制每個程序集中類型層次結構的內存布局。
程序集包含模塊,而模塊包含類型,類型又包含成員,反射則提供了封裝程序集、模塊和類型的對象。我們可以使用反射動態(tài)地創(chuàng)建類型的實例,將類型綁定到現(xiàn)有對象或從現(xiàn)有對象中獲取類型,然后調用類型的方法或訪問其字段和屬性。
那么究竟什么是反射呢?
反射(Reflection)是.NET中的重要機制,可以在運行時獲得.NET中每一個類型(包括類、結構、委托、接口和枚舉等)的成員,包括方法、屬性、事件、以及構造函數(shù)等。還可以獲得每個成員的名稱、限定符和參數(shù)等。有了反射,即可對每一個類型了如指掌。如果獲得了構造函數(shù)的信息,即可直接創(chuàng)建對象,即使這個對象的類型在編譯時還不知道。
二、反射的用途
1、使用Assembly定義和加載程序集,加載在程序集清單中列出模塊,以及從此程序集中查找類型并創(chuàng)建該類型的實例。
2、使用Module了解包含模塊的程序集以及模塊中的類等,還可以獲取在模塊上定義的所有全局方法或其他特定的非全局方法。
3、使用ConstructorInfo了解構造函數(shù)的名稱、參數(shù)、訪問修飾符(如public或private)和實現(xiàn)詳細信息(如abstract或virtual)等。使用Type的GetConstructors()或GetConstructor()方法來調用特定的構造函數(shù)。
4、使用MethodInfo了解方法的名稱,返回類型、參數(shù)、訪問修飾符(如public或private)和實現(xiàn)詳細信息(如abstract或virtual)等。使用Type的GetMethods()或GetMethod()方法來調用特定的方法。
5、使用FiledInfo了解字段的名稱、訪問修飾符(如public或private)和實現(xiàn)詳細信息(如static)等,并獲取或設置字段值。
6、使用EventInfo了解事件的名稱、事件處理程序數(shù)據(jù)類型、自定義屬性、聲明類型和反射類型等,添加或移除事件處理程序。
7、使用PropertyInfo了解屬性的名稱、數(shù)據(jù)類型、聲明類型、反射類型和只讀或可寫狀態(tài)等,獲取或設置屬性值。
8、使用ParameterInfo了解參數(shù)的名稱、數(shù)據(jù)類型、是輸入?yún)?shù)還是輸出參數(shù),以及參數(shù)在方法簽名中的位置等。
三、反射用到的命名空間及主要類
1、命名空間
System.Reflection
System.Type
System.Reflection.Assembly
2、反射用到的主要類
Type類:該類位于System.Type命名空間下面,通過這個類可以訪問任何給定數(shù)據(jù)類型的信息。
Assembly類:該類位于System.Reflection.Assembly命名空間下面,通過這個類可以訪問給定程序集的信息,或者把這個程序集加載到程序中。
四、Type類
Type類位于System.Type命名空間下面,通過這個類可以訪問關于任何數(shù)據(jù)類型的信息。
我們以前把Type看作一個類,但它實際上是一個抽象的基類。只要實例化了一個Type對象,實際上就實例化了Type的一個派生類。盡管一般情況下派生類只提供各種Type方法和屬性的不同重載,但是這些方法和屬性返回對應數(shù)據(jù)類型的正確數(shù)據(jù),Type有與每種數(shù)據(jù)類型對應的派生類。
它們一般不添加新的方法或屬性。通常,獲取指向任何給定類型的Type引用有3種常用方式:
1、使用GetType()方法,所有的類都會從System.Object繼承這個方法
string v = "abc"; Type type = v.GetType();
2、使用Type類的靜態(tài)方法GetType()
Type type2 = Type.GetType("System.string", false, true);
3、使用C#的typeof運算符,這個運算符的參數(shù)是類型的名稱(但不放在引號中)
var t = typeof(string);
運行結果:
注意:在一個變量上調用GetType()方法,不是把類型的名稱作為其參數(shù)。但要注意,返回的Type對象仍只與該數(shù)據(jù)類型相關。如果引用了一個對象,但不能確保該對象實際上是哪個類型的實例,這個方法就很有用。
4、Type類的屬性
由Type實現(xiàn)的屬性可以分為下述三類:
1)許多屬性都可以獲取包含與類相關的各種名稱的字符串
2)屬性還可以進一步獲取Type對象的引用,這些引用表示相關的類
3)許多Boolean 屬性表示這個類型是一個類、還是一個枚舉等。這些屬性包括IsAbstract、IsArray、IsClass、IsEnum、IsInterface、IsPointer、IsPrimitive(一種預定義的基本數(shù)據(jù)類型)、 IsPublic、IsSealed和IsValueType
5、Type類的方法
System.Type類的大多數(shù)方法都用于獲取對應數(shù)據(jù)類型的成員信息:構造函數(shù)、屬性、方法和事件等。它有許多方法,但它們都有相同的模式。
例如,有兩個方法可以獲取數(shù)據(jù)類型的方法信息:GetMethod() 和 GetMethods()。GetMethod()方法返回System.Reflection.MethodInfo對象的一個引用,其中包含一個方法的信息。GetMethods()返回這種引用的一個數(shù)組。其區(qū)別是GetMethods()返回所有方法的信息,而GetMethod()返回一個方法的信息,其中該方法包含特定的參數(shù)列表。這兩個方法都有重載方法,該重載方法有一個附加的參數(shù),BindingFlags枚舉值,表示應返回哪些成員,例如,返回公有成員、實例成員和靜態(tài)成員等。
Type的成員方法:
注意:GetMember() 和 GetMembers()方法返回數(shù)據(jù)類型的一個或所有成員的信息,這些成員可以是構造函數(shù)、屬性和方法等。最后要注意,可以調用這些成員,其方式是調用Type的InvokeMember()方法,或者調用MethodInfo, PropertyInfo和其他類的Invoke()方法。
五、Assembly類
Assembly類在System.Reflection名稱空間中定義,它允許訪問給定程序集的元數(shù)據(jù),它也包含可以加載和執(zhí)行程序集(假定該程序集是可執(zhí)行的)的方法。與Type類一樣,Assembly類包含非常多的方法和屬性。
在使用Assembly實例做一些工作前,需要把相應的程序集加載到正在運行的進程中。為此,可以使用靜態(tài)成員Assembly.Load()或Assembly.LoadFrom()這兩個方法的區(qū)別是:
Load()方法的參數(shù)是程序集的名稱,運行庫會在各個位置上搜索該程序集,試圖找到該程序集,這些位置包括本地目錄和全局程序集緩存。使用Load()方法前要添加程序集的引用。
LoadFrom()方法的參數(shù)是程序集的完整路徑名,它不會在其他位置搜索該程程序集。
例如:
Assembly assembly1 = Assembly.Load("Animal"); Assembly assembly1 = Assembly.LoadFrom(@"D:\Study\Practice\Animal.dll");
這兩個方法都有許多其他重載版本,它們提供了其他安全信息。加載了一個程序集后,就可以使用它的各種屬性進行查詢,例如,查找它的全名:
string name = assembly1.FullName;
Assembly類的一個功能是它可以獲得在相應程序集中定義的所有類型的詳細信息,只要調用Assembly以GetTypes()方法,它就可以返回一個包含所有類型的詳細信息的Type類型的引用數(shù)組:
Type[] types = assembly.GetTypes(); foreach (Type definedType in types) ( //處理代碼 )
六、使用反射實現(xiàn)上面的程序
經(jīng)過上面的講解,相信大家對反射有一定的了解了,下面將會使用反射實現(xiàn)開篇提到的應用場景,代碼如下:
using Animal; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Reflection; namespace ReflectionCon { class Program { static void Main(string[] args) { Console.WriteLine("請錄入動物類型:"); string type = Console.ReadLine().Trim(); // 創(chuàng)建程序集對象,靜態(tài)加載Animal程序集 前提:需要先添加對Animal程序集的引用 Assembly assembly = Assembly.Load("Animal"); // 獲取程序集中的類型(在這里指的就是Animal里面的類:即Cat、Dog、Pig、Bird類) Type[] types = assembly.GetTypes(); foreach (Type t in types) { // t.Name表示類名(即Cat、Dog、Pig、Bird) if (type == t.Name.ToLower()) { // 找到Shout方法 MethodInfo m = t.GetMethod("Shout"); // 創(chuàng)建對象 object o = Activator.CreateInstance(t); // 找屬性 PropertyInfo[] para = t.GetProperties(); // 遍歷屬性 foreach (PropertyInfo p in para) { // 輸出屬性的名字 即:Name和Age //Console.WriteLine(p.Name); if (p.Name == "Name") { // 給屬性賦值 p.SetValue(o, "張三", null); } if (p.Name == "Age") { // 獲取o對象的屬性為p的屬性值并加10 int age = Convert.ToInt32(p.GetValue(o)) + 10; // 給屬性賦值 p.SetValue(o, age, null); } } // 調用方法 m.Invoke(o, null); } } Console.ReadKey(); } } }
運行程序:
如果新增加一個動物類,只需要實現(xiàn)Animal抽象父類即可,而主程序不需要修改。
七、反射的優(yōu)缺點
1、反射的優(yōu)點
1)、反射提高了程序的靈活性和擴展性。
2)、降低耦合性,提高自適應能力。
3)、它允許程序動態(tài)創(chuàng)建和控制任何類的對象,無需提前硬編碼目標類。適用在程序集不固定的地方,通常和配置文件一起使用。
2、反射的缺點
1)、性能問題:使用反射基本上是一種解釋操作,用于字段和方法接入時要遠慢于直接代碼。因此反射機制主要應用在對靈活性和拓展性要求很高的系統(tǒng)框架上,普通程序不建議使用。
2)、使用反射會模糊程序內部邏輯;程序員希望在源代碼中看到程序的邏輯,反射卻繞過了源代碼的技術,因而會帶來維護的問題,反射代碼比相應的直接代碼更復雜。
到此這篇關于C#反射機制的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
C#使用is、as關鍵字以及顯式強轉實現(xiàn)引用類型轉換
這篇文章介紹了C#使用is、as關鍵字以及顯式強轉實現(xiàn)引用類型轉換的方法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08C#采用FileSystemWatcher實現(xiàn)監(jiān)視磁盤文件變更的方法
這篇文章主要介紹了C#采用FileSystemWatcher實現(xiàn)監(jiān)視磁盤文件變更的方法,詳細分析了FileSystemWatcher的用法,并以此為基礎實現(xiàn)監(jiān)視磁盤文件變更,是非常實用的技巧,具有一定的借鑒價值,需要的朋友可以參考下2014-11-11c# winform讀取xml文件創(chuàng)建菜單的代碼
動態(tài)創(chuàng)建菜單使得程序靈活性大大增加,本文根據(jù)讀取xml文件中的配置菜單項來動態(tài)創(chuàng)建菜單,代碼如下2013-09-09Unity中的靜態(tài)批處理和動態(tài)批處理操作
這篇文章主要介紹了Unity中的靜態(tài)批處理和動態(tài)批處理操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04WPF+ASP.NET?SignalR實現(xiàn)簡易在線聊天功能的示例代碼
這篇文章將以一個簡單的聊天示例,簡述如何通過WPF+ASP.NET?SignalR實現(xiàn)消息后臺通知,僅供學習分享使用,如有不足之處,還請指正2022-09-09