1、反射概念:api
一、概念:數組
反射,通俗的講就是咱們在只知道一個對象的外部而不瞭解內部結構的狀況下,經過反射這個技術可使咱們明確這個對象的內部實現。函數
在.NET中,反射是重要的機制,它能夠動態的分析程序集Assembly,模塊Module,類型Type等等,咱們在不須要使用new關鍵的狀況下,就能夠動態工具
建立對象,使用對象。下降代碼耦合性提升了程序的靈活性。那麼,反射是怎麼實現的呢?它的內部實現依賴於元數據。元數據,簡單來講,在
測試
公共語言運行時CLR中,是一種二進制信息,用來描述數據,數據的屬性環境等等的一項數據,那麼反射解析數據的內部實現經過元數據實現再優化
合適不過了。this
二、實例:spa
首先先寫一個你要反射的程序集:pwa
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace StudentClass { public class Student { public Student() { } public string Name { get; set; } public int Age { get; set; } public char Gender { get; set; } public string IdCard { get; set; } public string Address { get; set; } private string Mobile { get; set; } public void Eat() { Console.WriteLine("我今天吃啦好多東西"); } public void Sing() { Console.WriteLine("耶耶耶耶耶"); } public int Calculate(int a, int b) { return a + b; } private string PrivateMethod() { return "我是一個私有方法"; } } }
先來看一下程序街、模塊、以及類等信息。3d
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace ReflectionInvoke { class Program { static void Main(string[] args) { //獲取程序集信息 Assembly assembly = Assembly.LoadFile(@"E:\測試\StudentClass\StudentClass\bin\Debug\StudentClass.dll"); Console.WriteLine("程序集名字:"+assembly.FullName); Console.WriteLine("程序集位置:"+assembly.Location); Console.WriteLine("運行程序集須要的額CLR版本:"+assembly.ImageRuntimeVersion); Console.WriteLine("===================================================="); //獲取模塊信息 Module[] modules = assembly.GetModules(); foreach (Module item in modules) { Console.WriteLine("模塊名稱:"+item.Name); Console.WriteLine("模塊版本ID"+item.ModuleVersionId); } Console.WriteLine("======================================================"); //獲取類,經過模塊和程序集均可以 Type[] types = assembly.GetTypes(); foreach (Type item in types) { Console.WriteLine("類型的名稱:"+item.Name); Console.WriteLine("類型的徹底命名:"+item.FullName); Console.WriteLine("類型的類別:"+item.Attributes); Console.WriteLine("類型的GUID:"+item.GUID); Console.WriteLine("====================================================="); } //獲取主要類Student的成員信息等 Type studentType = assembly.GetType("StudentClass.Student");//徹底命名 MemberInfo[] mi = studentType.GetMembers(); foreach (MemberInfo item in mi) { Console.WriteLine("成員的名稱:"+item.Name); Console.WriteLine("成員類別:"+item.MemberType); } Console.WriteLine("====================================="); //獲取方法 BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance; MethodInfo[] methodInfo = studentType.GetMethods(flags); foreach (MethodInfo item in methodInfo) { Console.WriteLine("public類型的,不包括基類繼承的實例方法:"+item.Name); } Console.WriteLine("========================================"); BindingFlags flag = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic; MethodInfo[] methods = studentType.GetMethods(flag); foreach (MethodInfo item in methods) { Console.WriteLine("非public類型的,不包括基類繼承的實例方法:"+item.Name); } Console.WriteLine("========================================"); //獲取屬性 BindingFlags flags2 = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance; PropertyInfo[] pi = studentType.GetProperties(flags2); foreach (PropertyInfo item in pi) { Console.WriteLine("屬性名稱:"+item.Name); } } } }
結果:
一、Assembly.Load()以及Assembly.LoadFile():
LoadFile這個方法的參數是程序集的絕對路徑,經過點擊程序集shift+鼠標右鍵複製路徑便可。load方法有多個重載,還能夠經過流的方式獲取程序集,
在項目中,主要用來取相對路徑,由於不少項目的程序集會被生成在一個文件夾裏,此時取相對路徑不容易出錯。
二、GetTypes和GetType():
很明顯第一個獲取程序集下全部的類,返回一個數組,第二個要有參數,類名爲徹底類名:命名空間+類名,用於獲取指定的類。
三、Type類下能夠獲取這個類的全部成員,也能夠獲取字段屬性方法等,有:
ConstructorInfo獲取構造函數, FieldInfo獲取字段, MethodInfo獲取方法,PropertyInfo獲取屬性,EventInfo獲取事件,ParameterInfo獲取參數,經過他們的
Get***獲取,加s獲取全部返回數組,不加s獲取具體的。
四、BindFlags:用於對獲取的成員的類型加以控制:
經過反編譯工具,能夠看到這個enum的具體:
BindingFlags.Public公共成員,NonPublic,非公有成員,DeclaredOnly僅僅反射類上聲明的成員不包括簡單繼承的成員。CreateInstance調用構造函數,GetField獲取字段值對setField無效。還有不少讀者能夠F12打開看一下用法以及註釋。注意必須指定:BindingFlags.Instance或BindingFlags.Static,主要爲了獲取返回值,是靜態的仍是實例的。
2、反射的運用:
一、建立實例:
建立實例大致分爲2種,Activator.CreateInstance和Assembly.CreateInstance。這2種方法均可以建立實例,可是又有區別,下面來經過實例具體說明。
首先分析第一種Activator.CreateInstance
這個方法有許多的重載,最經常使用的2種:(Type type)和(Type type,params object[] obj)第一種調用無參構造,第二種調用有參構造
在前面的實例Student中添加一個有參構造:
public Student(string name) { this.Name = name; }
而後反射建立實例
Assembly assmbly = Assembly.LoadFile(@"E:\測試\StudentClass\StudentClass\bin\Debug\StudentClass.dll"); Type studentType = assmbly.GetType("StudentClass.Student"); object obj = Activator.CreateInstance(studentType, new object[] { "milktea" }); if (obj != null) { Console.WriteLine(obj.GetType()); }
這裏就建立了一個實例,如今讓咱們用反編譯工具查看它的底層實現:
public static object CreateInstance(Type type, params object[] args) { return CreateInstance(type, BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance, null, args, null, null); }
調用它的參數最多的一個重載後,發現他調用了下面這個方法:
這裏咱們就能夠知道這裏建立實例和new建立實例的第三步實現相同,new建立實例,先在堆中開闢新空間,而後建立對象調用它的構造函數,
因此咱們能夠知道Activator.CreateInstance的底層仍然是經過被調用的類別的構造建立的,那麼若是沒有參數就說明調用的是無參構造。
而後來看第二種Assembly.CreateInstance:
Assembly assmbly = Assembly.LoadFile(@"E:\測試\StudentClass\StudentClass\bin\Debug\StudentClass.dll"); Type studentType = assmbly.GetType("StudentClass.Student"); object o = assmbly.CreateInstance(studentType.FullName,true); Console.WriteLine(o.GetType());
運行程序,卻發現此時拋出了MissingMethodException異常:
但是明明有一個構造函數,爲何還會說沒有找到構造函數呢?
經過反編譯工具,來看看什麼緣由:
咱們發現Assembly這個類下的CreateInstance方法,竟然返回的是Activator下的CreateInstance方法,那麼就只有一種可能,他調用的
是反射類下的無參構造,而無參構造被咱們新加的有參構造給替代了,所以也就找不到無參構造,爲了證實結論的正確,咱們把無參構造
加上,而後從新實驗:
public Student() { }
果真和咱們預想的同樣,若是沒有無參構造,那麼使用Assembly類下的方法就會拋出異常。綜合2種狀況,既然Assembly下的CreateInstance
也是調用的Activator的方法,而且Assembly限制更大,那咱們在建立實例的時候應當仍是選Activator下的方法更不容易出錯,不是嗎。
二、調用方法,屬性賦值等
建立了實例之後,就到了實際用途,怎麼調用它的方法,怎麼給它的字段賦值,怎麼添加一個委託事件等,如今來看。
A、第一種方法:使用Type類的InvokeMember()方法,實例以下:
Assembly assmbly = Assembly.LoadFile(@"E:\測試\StudentClass\StudentClass\bin\Debug\StudentClass.dll"); Type studentType = assmbly.GetType("StudentClass.Student"); object o = assmbly.CreateInstance(studentType.FullName, true); Type instanceType = o.GetType(); //給屬性賦值並檢查 instanceType.InvokeMember("Name",BindingFlags.SetProperty,null,o,new object[]{"milktea"}); string propertyValue = instanceType.InvokeMember("Name",BindingFlags.GetProperty,null,o,null).ToString(); Console.WriteLine(propertyValue); //調用方法無返回值 instanceType.InvokeMember("Eat",BindingFlags.InvokeMethod,null,o,null); //調用方法有返回值 int sum = Convert.ToInt32(instanceType.InvokeMember("Calculate",BindingFlags.InvokeMethod,null,o,new object[]{2,3})); Console.WriteLine(sum);
幾個重要的參數:第一個方法的名稱,Enum的值,字段SetField,方法InvokeMethod,而後選擇要使用的對象,即剛纔反射建立的實例,最後一個要
賦的值或者方法參數等必須爲一個object數組。
這個方法詳情請看MSDN官方文檔:
B、 第二種方法:使用FiledInfo,MethodInfo...等的Invoke方法,實例以下:
Assembly assmbly = Assembly.LoadFile(@"E:\測試\StudentClass\StudentClass\bin\Debug\StudentClass.dll"); Type studentType = assmbly.GetType("StudentClass.Student"); object o = assmbly.CreateInstance(studentType.FullName, true); Type instanceType = o.GetType(); //給屬性賦值並檢查 PropertyInfo ps = instanceType.GetProperty("Age",typeof(Int32)); ps.SetValue(o,5,null); PropertyInfo pi2 = instanceType.GetProperty("Age"); Console.WriteLine(pi2.GetValue(o,null)); //調用方法 MethodInfo mi = instanceType.GetMethod("Calculate", BindingFlags.Instance|BindingFlags.Public); object obj = mi.Invoke(o, new object[] { 1, 2 }); int result = Convert.ToInt32(mi.Invoke(o,new object[]{1,2})); Console.WriteLine(result);
方法的過程即先經過方法名取的方法,注意參數中的BindingFlags的2個參數都不能夠丟,不然會報空引用異常,而後Invoke方法中
第一個參數爲反射建立的對象,第二個參數爲賦的值,或參數等。
C、第三種方法:對於反射的優化,經過使用委託:這裏咱們將使用Stopwatch對比和上次一樣結果的時間:
Assembly assmbly = Assembly.LoadFile(@"E:\測試\StudentClass\StudentClass\bin\Debug\StudentClass.dll"); Type studentType = assmbly.GetType("StudentClass.Student"); object o = assmbly.CreateInstance(studentType.FullName, true); Type instanceType = o.GetType(); //給屬性賦值並檢查 Stopwatch sw = new Stopwatch(); sw.Start(); PropertyInfo ps = instanceType.GetProperty("Age",typeof(int)); ps.SetValue(o,5,null); PropertyInfo pi2 = instanceType.GetProperty("Age"); Console.WriteLine(pi2.GetValue(o,null)); Console.WriteLine("屬性沒啓用優化:"+sw.Elapsed); //調用方法 sw.Reset(); sw.Restart(); MethodInfo mi = instanceType.GetMethod("Calculate", BindingFlags.Instance|BindingFlags.Public); object obj = mi.Invoke(o, new object[] { 1, 2 }); int result = Convert.ToInt32(mi.Invoke(o,new object[]{1,2})); Console.WriteLine(result); Console.WriteLine("方法沒啓用優化:" + sw.Elapsed); //給屬性賦值並檢查 sw.Reset(); sw.Restart(); PropertyInfo pi3 = instanceType.GetProperty("Age", typeof(int)); var piDele = (Action<int>)Delegate.CreateDelegate(typeof(Action<int>),o,pi3.GetSetMethod()); piDele(5); var result1 = (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), o, pi3.GetGetMethod()); Console.WriteLine(result1()); Console.WriteLine("屬性啓用優化:"+sw.Elapsed); //調用方法 sw.Reset(); sw.Restart(); MethodInfo mi2 = instanceType.GetMethod("Calculate",BindingFlags.Instance|BindingFlags.Public); var miDele = (Func<int, int, int>)Delegate.CreateDelegate(typeof(Func<int,int,int>),o,mi2); int a = miDele(1,2); Console.WriteLine(a); Console.WriteLine("方法啓用優化:"+sw.Elapsed);
這裏能夠很明顯的看到使用優化之後,時間縮短了斤2/3,試想一下,這裏只用了不多的代碼,若是代碼量不少的話就能夠節省更多的時間。
固然也能夠看出這裏的代碼量比較大而複雜,能夠說不夠漂亮簡介,用空間換取效率,Delegate.CreateDelegate()方法具體請看: 詳情連接
D、如今將最後一種,.NET 4.0出現了一個新的關鍵字:dynamic,和var有點相似的感受,但實則不一樣。var是語法糖,在代碼編譯期就將真正的類型
已經替換了,Visual Studio能夠推斷出var的類型,而dynamic不會在編譯期檢查,被編譯爲object類型,而會在運行期作檢查,而且這個效率雖然沒
有優化後的反射快,但比普通的反射也要快一些。
Stopwatch watch1 = Stopwatch.StartNew(); Type type = Assembly.LoadFile(@"E:\C#優化實例\StudentClass\StudentClass\bin\Debug\StudentClass.dll").GetType("StudentClass.Student"); Object o1 = Activator.CreateInstance(type,new object[]{12}); var method1 = type.GetMethod("Add",BindingFlags.Public|BindingFlags.Instance); int num1 = (int)method1.Invoke(o1,new object[]{1,2}); Console.WriteLine(num1); Console.WriteLine("反射耗時"+watch1.ElapsedMilliseconds); Stopwatch watch2 = Stopwatch.StartNew(); Type type2 = Assembly.LoadFile(@"E:\C#優化實例\StudentClass\StudentClass\bin\Debug\StudentClass.dll").GetType("StudentClass.Student"); dynamic o2 = Activator.CreateInstance(type, new object[] { 13 }); int num2 = o2.Add(2,3); Console.WriteLine(num2); Console.WriteLine("dynamic耗時:"+watch2.ElapsedMilliseconds);
這裏看到是比反射要快一些,並且代碼精簡了不少。綜合考慮下來,代碼精簡度以及耗費時間,建議儘可能使用dynamic關鍵字來處理反射。
這裏反射的主要點總結完畢,還有不全的方面請評論留言相告,感激感激 2018-11-08 17:29:28