什么是反射
反射總結目錄
什么是反射
程序運行時將exe、dll文件加載到內存并執行一些操作的過程,這個過程稱為反射。
通常所說的反射是實現這個過程所使用的技術手段,.Net中System.Reflection等命名空間提供了反射的實現。反射的原理
通過對程序集元數據的搜索找到對應的成員類型并使用,以實現驗證或動態調用程序集的目的。一個簡單的例子引入反射
下面這簡單例子引入反射的使用,這個例子中定義了一個Hello類并添加一個Say方法,我將使用反射調用Say方法。
namespace ReflectionStudy {public class Hello{public void Say(){Console.WriteLine("Hello Reflection!");}} }//使用反射技術調用Say方法 //1.從當前程序集中查找Hello類 var helloType= Assembly.GetExecutingAssembly().GetType("ReflectionStudy.Hello"); //2.獲取Hello類的Say方法 var method = helloType.GetMethod("Say"); //3.創建Hello類的實例 var helloInstance=Activator.CreateInstance(helloType); //4.執行Say方法 method.Invoke(helloInstance, null);System.Reflection命名空間
上面的例子雖然簡單,但是已足夠說明反射的大致流程:
1. 加載程序集
因為程序集是個比較大的概念,而這偏離了這篇文章的主題,請移步我的另一篇文章《程序集》。
2. 發現類型
FCL提供了許多API來獲取程序集中的類型,目前常用的API是Assembly中的ExportedTypes、DefinedTypes、GetType等,ExportedTypes屬性用來獲取公開方法即public類型,DefiedTypes屬性用來獲取所有類型,GetType方法獲取一個指定的類型。
var assembly = Assembly.Load(@"mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); int index=0; Console.WriteLine("獲取程序集:{0} 中的ExportedTypes",assembly); foreach (var type in assembly.ExportedTypes) {Console.WriteLine("{0}. {1}", ++index,type); } Console.WriteLine("ExportedTypes類型有{0}個",assembly.ExportedTypes.Count()); index = 0; Console.WriteLine("獲取程序集:{0} 中的DefinedTypes", assembly); foreach (var type in assembly.DefinedTypes) {Console.WriteLine("{0}. {1}", ++index, type); } Console.WriteLine("DefinedTypes類型有{0}個", assembly.ExportedTypes.Count());3. 構造類型實例
在FCL中提供了幾個構造類型實例的機制分散在System.Activator,System.AppDomain,System.Reflection.ConstructorInfo中,如果查看源碼的話可以看到內部實現調用的都是Activator.CreateInstance。
下面的例子演示實例的創建 public class Hello {public void Say(){Console.WriteLine("Hello Reflectioin!");} } //出于演示的目的下面這句簡化了加載程序集的步驟 Type helloType = typeof(Hello); //創建實例 var hello=Activator.CreateInstance(helloType);4. 發現類型成員
在FCL中有反射提供了一個類型基類System.Reflection.MemberInfo,其派生類如下圖所示:
通常使用Type類型來發現成員類型如:Method,Filed,Property,Event等。
 .Net 4中可以通過Type.GetTypeInfo擴展方法獲取TypeInfo對象以便獲取更多功能,相比Type類型TypeInfo代價更高。
下面通過例子來說明如何發現成員:
成員類型描述: * 一個事件OnSay * 兩個字段,_name是private,Age是Public * 一個Name屬性 * 三個方法,SayHello是Static public,Say是public,HaHa是private using System;namespace ReflectionAssembly {public class Hello{//事件public event Action OnSay;//私有字段private string _name;//共有字段public string Age;//屬性public string Name{get{return _name;}set{_name = value;}}//靜態方法public static void SayHello(){Console.WriteLine("SayHello");}//實例方法public void Say(){Console.WriteLine("Hello Reflectioin!");}//測試事件public void TestEvent(){if (OnSay != null){Console.WriteLine($"OnSay綁定了{OnSay.GetInvocationList().Length}個方法");//遍歷執行綁定的事件foreach (Action onSay in OnSay.GetInvocationList()){onSay();}}}//私有實例方法void HaHa(){Console.WriteLine("HaHa");}} }因為MemberInfo是所有成員類型的基類,那么我們先看一下獲取所有的成員類型:
static void Main(string[] args) {var helloType = Assembly.Load("ReflectionAssembly").GetType("ReflectionAssembly.Hello");var memberInfos = helloType.GetMembers();foreach (var memberInfo in memberInfos){Console.WriteLine($"成員類型:{memberInfo.MemberType}\t類型名稱:{memberInfo.Name}");}Console.WriteLine($"類型{helloType.FullName}共{memberInfos.Count()}個成員");Console.Read(); }//運行結果如下: 成員類型:Method 類型名稱:add_OnSay 成員類型:Method 類型名稱:remove_OnSay 成員類型:Method 類型名稱:get_Name 成員類型:Method 類型名稱:set_Name 成員類型:Method 類型名稱:SayHello 成員類型:Method 類型名稱:Say 成員類型:Method 類型名稱:ToString 成員類型:Method 類型名稱:Equals 成員類型:Method 類型名稱:GetHashCode 成員類型:Method 類型名稱:GetType 成員類型:Constructor 類型名稱:.ctor 成員類型:Property 類型名稱:Name 成員類型:Event 類型名稱:OnSay 成員類型:Field 類型名稱:Age 類型ReflectionAssembly.Hello共14個成員Why?我們只定義了7個成員,結果卻顯示14個?
- 如果你了解Event類型那么可以忽略add_OnSay,remove_OnSay。《事件和委托學習總結》
- 如果你了解Property那么可以忽略get_Name,set_Name。
- 如果你了解Object那么可以忽略ToString,Equals,GetHasCode,GetType。(所有的引用類型都繼承Object)
- 如果你知道默認構造函數那么可以忽略.ctor。
- 現在剩下的OnSay,Age,Name,Say,SayHello5個類是我們定義的,還有2個_name,Haha沒有在上面運行結果中顯示,我們發現這個成員有個共同點是:他們都是私有成員。
如何獲取私有成員
 我們將代碼稍作改動看看效果:
GetMembers,FindMembers內部實現
Type類型同時也提供FindMembers方法來獲取成員類型,如果觀察其內部實現會發現它們僅僅是一個包裝方法, 它們通過包裝GetMethods,GetFields,GetProperties,GetEvents,GetConstructors方法的實現來獲取所有的成員信息。以下方法均提供了多個重載方法
發現字段(FildInfo)
helloType.GetFields();發現屬性(PropertyInfo)
helloType.GetProperties();發現方法(MethodInfo)
helloType.GetMethods();發現構造器(ConstructorInfo)
helloType.GetConstructors();發現事件(EventInfo)
helloType.GetEvents();5. 執行對類的操作
調用字段
字段類型通過GetValue,SetValue方法對來操作
//實例化Hello var hello = Activator.CreateInstance(helloType); //調用private字段 var _name = helloType.GetField("_name",BindingFlags.NonPublic|BindingFlags.Instance); _name.SetValue(hello, "guodf"); Console.WriteLine(_name.GetValue(hello)); //調用public字段 var age = helloType.GetField("Age"); age.SetValue(hello, "16"); Console.WriteLine(age.GetValue(hello));調用屬性
屬性類型通過get_**,set_**方法來操作
//實例化Hello var hello = Activator.CreateInstance(helloType); //調用屬性 var name = helloType.GetProperty("Name"); name.SetValue(hello, "guodf test"); Console.WriteLine($"_name:{_name.GetValue(hello)}\tName:{name.GetValue(hello)}");調用方法
方法通過Invoke執行方法
//調用實例方法 var say = helloType.GetMethod("Say"); say.Invoke(hello,null); //調用private實例方法 var haha = helloType.GetMethod("HaHa", BindingFlags.Instance | BindingFlags.NonPublic); haha.Invoke(hello, null);調用事件
//調用事件 var testEvent = helloType.GetMethod("TestEvent"); var onSay = helloType.GetEvent("OnSay"); Action event1 = () => { Console.WriteLine("event1"); }; Action event2 = () => { Console.WriteLine("event2"); }; //綁定2個方法 onSay.AddEventHandler(hello, event1); onSay.AddEventHandler(hello, event2); testEvent.Invoke(hello,null); //移除一個方法 onSay.RemoveEventHandler(hello, event1); testEvent.Invoke(hello,null);調用靜態方法
var sayHello=hello.GetMethod("SayHello"); sayHello.Invoke(null, null);隱式反射和顯式反射
C#中有隱式轉換和顯式轉換得概念,通常派生類轉換為基類型被稱為隱私轉換,因為可以直接將派生類型賦值給基類型;反之稱為顯示轉換。那么在反射得使用過程中,我通常使用兩種實現方式來使用反射對象:一種基于接口的編程方式,另一種則是完全的字符串查找方式。所以我將基于接口的方式稱為顯式反射,這種做法的好處是編程期間我們可以直接使用類型的方法;而另一種基于字符串找好的方式我稱它為隱式反射,因為在使用過程中無論得到那種成員類型都是通過字符串查找實現的。
反射的優缺點
優點
1. 動態加載,按需加載 2. 解耦缺點
1. 無編譯期類型安全檢查 2. 性能低小結
反射一種技術,這種技術可以幫助我們實現一些看起來很酷的編程設計,但這種技術并不完美,它犧牲了效率換來了靈活性。至于這種犧牲的價值當然是仁者見仁智者見智。
轉載于:https://www.cnblogs.com/guodf/p/6585566.html
總結
 
                            
                        - 上一篇: bzoj 3277 串 后缀树+子树不
- 下一篇: Linux之nfs服务
