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

WPF+ASP.NET?SignalR實(shí)現(xiàn)簡(jiǎn)易在線聊天功能的示例代碼