6.C#知識點:反射
1.反射是什麽?
反射提供描述組件,模塊和類型的對象(類型為Type)。您可以使用反射來動態創建類型的實例,將類型綁定到現有對象,或從現有對象獲取類型,並調用其方法或訪問其字段和屬性。如果您在代碼中使用屬性,反射使您可以訪問它們。有關更多信息,請參閱屬性。-----來自微軟官方。
微軟的解釋我覺得還可以。用大白話講就是我們可以以通過反射讓我們知道位置類型的信息。類似顯示生活中的B超啊。醫生用B超看到孕婦肚子裏的內部情況,因為醫生無法從內部查看。反射也是一樣,對於位置類型。或者引用過來的dll。我們是不知道內部情況的。但是可以通過反射。蝙蝠的超聲波也是。通過聲波反射回來,得知前方是否有障礙。這就是反射的功能。如果要問反射內部是如何實現的。不好意思。目前我也不知道。哈哈哈哈。
簡單的來說,我們的程序是有dll的組成的,dll裏面有許許多多的類組成。類裏面又有字段,屬性和方法。反射的作用就是給個dll就能知道有哪些類,通過類又能知道有哪些成員。那麽.net裏面的反射是怎麽做到呢?那下面就要介紹幾個種類的反射類了。
(1)使用Assembly定義和加載程序集,加載在程序集清單中列出模塊,以及從此程序集中查找類型並創建該類型的實例。
(2)使用Module了解包含模塊的程序集以及模塊中的類等,還可以獲取在模塊上定義的所有全局方法或其他特定的非全局方法。
(3)使用ConstructorInfo了解構造函數的名稱、參數、訪問修飾符(如pulic 或private)和實現詳細信息(如abstract或virtual)等。
(4)使用MethodInfo了解方法的名稱、返回類型、參數、訪問修飾符(如pulic 或private)和實現詳細信息(如abstract或virtual)等。
(5)使用FiedInfo了解字段的名稱、訪問修飾符(如public或private)和實現詳細信息(如static)等,並獲取或設置字段值。
(6)使用EventInfo了解事件的名稱、事件處理程序數據類型、自定義屬性、聲明類型和反射類型等,添加或移除事件處理程序。
(7)使用PropertyInfo了解屬性的名稱、數據類型、聲明類型、反射類型和只讀或可寫狀態等,獲取或設置屬性值。
(8)使用ParameterInfo了解參數的名稱、數據類型、是輸入參數還是輸出參數,以及參數在方法簽名中的位置等。這段話是從大牛的博客拷貝------->傳送門
下面我依次重點的介紹幾個詳細的類。
首先是Assembly。這個存在於System.Reflection命名空間下面。我主要講它的3個加載程序集的的方法和區別。Load,LoadForm,LoadFile。
講了這門多文字,先從代碼看看語法。
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace 反射Demo { class Program { static void Main(string[] args) { //加載程序集 Assembly assembly = Assembly.Load("TestDLL"); //輸出程序集的強名稱 Console.WriteLine(assembly.FullName); Console.ReadKey(); } } }
Load方法就是通過程序集的的名稱加載程序,但是需要要加載的程序集在當前程序集的bin目錄下才能找得到。
LoadForm
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace 反射Demo { class Program { static void Main(string[] args) { #region Load方法 //加載程序集 // Assembly assembly = Assembly.Load("TestDLL"); //輸出程序集的強名稱 //Console.WriteLine(assembly.FullName); //Console.ReadKey(); #endregion Assembly assmbly1 = Assembly.LoadFrom(@"C:\Users\DH\Documents\visual studio 2017\Projects\反射Demo\TestDLL\bin\Debug\TestDLL.dll"); Console.WriteLine(assmbly1.FullName); Console.ReadKey(); } } }
LoadForm是通過路徑進行創建。返回加載的程序集。
來看最後一個loadFile
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace 反射Demo { class Program { static void Main(string[] args) { #region Load方法 //加載程序集 // Assembly assembly = Assembly.Load("TestDLL"); //輸出程序集的強名稱 //Console.WriteLine(assembly.FullName); //Console.ReadKey(); #endregion #region LoadFrom方法 //Assembly assmbly1 = Assembly.LoadFrom(@"C:\Users\DH\Documents\visual studio 2017\Projects\反射Demo\TestDLL\bin\Debug\TestDLL.dll"); //Console.WriteLine(assmbly1.FullName); ///Console.ReadKey(); #endregion #region LoadFile方法 Assembly assmbly2 = Assembly.LoadFile(@"C:\Users\DH\Documents\visual studio 2017\Projects\反射Demo\TestDLL\bin\Debug\TestDLL.dll"); Console.WriteLine(assmbly2.FullName); Console.ReadKey(); #endregion } } }
這個三個方法語法都很簡單。現在來說說這個三個的區別,和優缺點。
LoadFrom和Load:LoadForm同一個程序集只加載一次,就算加載了不通的路徑相同的程序集,他也只能給你返回之前的程序集,LoadFrom只能用於加載不同標識的程序集, 也就是唯一的程序集, 不能用於加載標識相同但路徑不同的程序集。
LoadFile和LoadForm:loadFile只會加載本身程序集,不會加在其引用的程序集,LoadForm和Load是會加載的其引用程序集。而且LoadFile同一個程序集只能加載一次。這個和LoadForm是一樣。
從性能上看 LoadForm沒有Load好,功能上也是load強一點。應用的時候如果loadFom和load都滿足需求,建議用load。
這幾個方法還幾個重載版本。由於本片只是基礎篇。篇幅不宜過多。想深入了解的小夥伴可以查詢MSN看看文檔。最詳細的說明就是文檔。但是文檔說的比較官方。結合大牛們寫的博客。更容易懂一點。
好了我們開始下一個階段了。程序集現在我們得了。我開始看看程序集裏面有些啥?
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace 反射Demo { class Program { static void Main(string[] args) { #region LoadFrom方法 Assembly assmbly = Assembly.LoadFrom(@"C:\Users\DH\Documents\visual studio 2017\Projects\反射Demo\TestDLL\bin\Debug\TestDLL.dll"); Type[] types = assmbly.GetTypes(); foreach (var item in types) { Console.WriteLine("類:"+item.Name); } Console.ReadKey(); #endregion } } }
assmbly.GetTypes()這個方法或獲取了所有類。
下面我我展示下獲取字段和方法的字段
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace 反射Demo { class Program { static void Main(string[] args) { #region LoadFrom方法 Assembly assmbly = Assembly.LoadFrom(@"C:\Users\DH\Documents\visual studio 2017\Projects\反射Demo\TestDLL\bin\Debug\TestDLL.dll"); Type[] types = assmbly.GetTypes(); foreach (var classitem in types) { Console.WriteLine("類:"+ classitem.Name); foreach (var fileditem in classitem.GetFields()) { Console.WriteLine("字段名:"+ fileditem.Name); } foreach (var methodItem in classitem.GetMethods()) { Console.WriteLine("方法名:"+methodItem.Name); } } Console.ReadKey(); #endregion } } }
可以看的出來我們將字段和方法名都給反射出來了,但是有個問題就是我們父類的方法也給弄出來了。我們可以修改下。這個地方有個重載版本。GetMethods有個重載方法可以通過BindingFlags枚舉參數進行篩選父類的方法,或者其他的。 BindingFlags這個枚舉類型。這裏就不多講。未來我會慢慢補全。
簡單的就是這麽多了。反射能做好多事情,非常靈活。我們抽象工廠裏面就會用到反射。我們的工廠。就是通過反射創建出來。下面我寫個demo演示下其作用。
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using TestDLL; namespace 反射Demo { class Program { static void Main(string[] args) { Assembly assmbly = Assembly.Load("TestDLL"); SqlServerHelper helper =(SqlServerHelper)assmbly.CreateInstance("TestDLL.SqlServerHelper"); helper.Query(); Console.ReadKey(); } } }
小夥伴們可以發現。我們實例化了一個SqlServerHelper對象,但是我們沒有用正常的new方法,而是用了反射。這個時候有的小夥伴可能就會說這是脫褲子放屁,直接new多簡單。在這裏是沒錯,但是放在真正的項目裏直接new是當時爽,需求變動就等著哭吧,比如說以後領導對你說,公司的數據庫不用SqlServer了,換成Oracle了。這是時候如果你是new的話還要改這裏的代碼,實際情況可能更復雜。但是用了我們的反射,這種煩惱就不會存在了。
CreateInstance("TestDLL.SqlServerHelper") 這個方法參數我們完全可以通過配置文件修改。這個類就不需要變了。我來演示下代碼
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TestDLL { public interface IDBHelper { void Query(); } }
先創建一個約定數據操作的接口 IDBHlper類。規定有一個Query方法,然後讓SqlServerHelper繼承這個接口,並且實現這個方法。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TestDLL { public class SqlServerHelper: IDBHelper { public void Query() { Console.WriteLine("這是測試"); } } }
然後修改mian函數的方法
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <appSettings> <add key="Helper" value="TestDLL.SqlServerHelper"/> </appSettings> </configuration>
using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using TestDLL; namespace 反射Demo { class Program { static void Main(string[] args) { Assembly assmbly = Assembly.Load("TestDLL"); string helperkey = ConfigurationManager.AppSettings["Helper"]; IDBHelper helper =(IDBHelper)assmbly.CreateInstance(helperkey); helper.Query(); Console.ReadKey(); } } }
這時候如果要將mian函數裏面helper切換成Oracle的只要添加一個繼承於IDBheper接口的類,然後實現方法,在修改配置文件指向這個類,然後就可以了。對於main函數我們是一點不需要動的,這是就是我們所有的高內聚低耦合。完成解耦。易於修改。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TestDLL { public class OracleHelper : IDBHelper { public void Query() { Console.WriteLine("我是Orcle數據庫"); } } }
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <appSettings> <add key="Helper" value="TestDLL.OracleHelper"/> </appSettings> </configuration>
using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using TestDLL; namespace 反射Demo { class Program { static void Main(string[] args) { Assembly assmbly = Assembly.Load("TestDLL"); string helperkey = ConfigurationManager.AppSettings["Helper"]; IDBHelper helper =(IDBHelper)assmbly.CreateInstance(helperkey); helper.Query(); Console.ReadKey(); } } }
mian函數一點都沒有變化。運行查看結果
到了這個裏,我已經演示了一個反射應用場景了。其實VS本身就很多地方用了反射。比如。創建對象調用方法時候VS直接只能幫我們列出這個對象下的成員。這個就是通過反射。其實還有很多。等待大家去發現。反射應該屬於C#裏面的高級知識點了。目前所說的只是冰山一角。
Ok。講到這裏就結束了哈。
如果剛開始學習的小夥伴還有疑問的話,可以評論咱們一起學習。
如果哪位大牛隨便瞄到個錯誤,也請告之我,讓我能夠進步。
6.C#知識點:反射