這裡我又唠叨幾句,大家在學習的時候,如看書或者看視頻時覺得非常爽,因為感覺基本都看得懂也都挺容易的,其實看懂是一回事,你自己會動手做出來是一回事,自己能夠說出來又是另一回事了。應該把學到的東西變成自己的東西,而不是依樣畫瓢。
在說反射之前,我們先來了解一下什麼是程序集?
程序集是.net中的概念,程序集可以看作是給一堆相關類打一個包,相當於java中的jar包。
程序集包含:
exe與dll的區別。
exe可以運行,dll不能直接運行,因為exe中有一個main函數(入口函數)。
類型元數據這些信息可以通過AssemblyInfo.cs文件來自定義。在每一個.net項目中都存在一個AssemblyInfo.cs文件,代碼格式:
using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // 有關程序集的常規信息通過以下 // 特性集控制。更改這些特性值可修改 // 與程序集關聯的信息。 [assembly: AssemblyTitle("ReflectedDemo")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ReflectedDemo")] [assembly: AssemblyCopyright("Copyright © 2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // 將 ComVisible 設置為 false 使此程序集中的類型 // 對 COM 組件不可見。 如果需要從 COM 訪問此程序集中的類型, // 則將該類型上的 ComVisible 特性設置為 true。 [assembly: ComVisible(false)] // 如果此項目向 COM 公開,則下列 GUID 用於類型庫的 ID [assembly: Guid("7674d229-9929-4ec8-b543-4d05c6500863")] // 程序集的版本信息由下面四個值組成: // // 主版本 // 次版本 // 生成號 // 修訂號 // // 可以指定所有這些值,也可以使用“生成號”和“修訂號”的默認值, // 方法是按如下所示使用“*”: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
這些信息在哪裡體現呢?就在我們程序集的屬性當中進行體現
我們平時在安裝一些CS客戶端程序的時候,在安裝目錄下面會看見許多的程序集文件。
使用程序集的好處
如何添加程序集的引用?
直接添加程序集路徑或者添加解決方案中的項目引用。
當我們需要擴展一個程序的時候,你可能會直接在原有的項目中進行添加,那這樣的話,如果你的這些代碼想共享給別人使用呢?你就可以打包成一個程序集,然後別人只要通過引用你這個程序集就可以進行擴展了。像我們常見的.net第三方框架庫,如log4net、unity等等。
注意:不能添加循環引用
什麼是添加循環引用?就是說A項目如果添加了B項目的項目引用,那麼此時B項目不能再添加A項目的項目引用,也就是說添加項目引用時,必須是單向的,像我們常見的三層框架之間的項目引用。
關於反射,你只要是做.net開發,你就一定天天在用。因為VS的智能提示就是通過應用了反射技術來實現的,還有我們常用的反編譯神器Reflector.exe,看它的名字就知道了。項目中比較常見的,是通過結合配置文件來動態實例化對象,如切換數據庫實例,或者Sprint.net的通過配置文件來實現依賴注入等。
反射技術其實就是動態獲取程序集的元數據的功能,反射通過動態加載dll,然後對其進行解析,從而創建對象,調用成員。
Type是對類的描述,Type類是實現反射的一個重要的類,通過它我們可以獲取類中的所有信息,包括方法、屬性等。可以動態調用類的屬性、方法。
反射的出現讓創建對象的方式發生了改變,因為過去面完創建對象都是直接通過new。
dll裡面有兩部分東西:IL中間語言和metadate元素據。
在.NET中反射用到命名空間是System.Reflection,這裡我先通過一個Demo來看反射能做些什麼
1、 新建控制台項目ReflectedDemo
2、 新建類庫項目My.Sqlserver.Dal
新建兩個類SqlServerHelper和SqlCmd,前者為共有類,後者為私有類
namespace My.Sqlserver.Dal { public class SqlServerHelper { private int age = 16; public string Name { get; set; } public string Query() { return string.Empty; } } class SqlCmd { } }
3、 項目ReflectedDemo,添加My.Sqlserver.Dal的項目引用,我這樣做的目的是為了方便項目ReflectedDemo中的bin目錄中時刻存在My.Sqlserver.Dal.dll程序集。
using System; using System.Reflection; namespace ReflectedDemo { class Program { static void Main(string[] args) { //加載程序集文件,在bin目錄中查找 Assembly assembly = Assembly.Load("My.Sqlserver.Dal"); Console.WriteLine("----------------Modules----------------------"); var modules = assembly.GetModules(); foreach(var module in modules) { Console.WriteLine(module.Name); } Console.WriteLine("----------------Types----------------------"); var types = assembly.GetTypes(); //獲取程序集中所有的類型,包括公開的和不公開的 foreach(var type in types) { Console.WriteLine(type.Name); Console.WriteLine(type.FullName); var members= type.GetMembers(); //獲取Type中所有的公共成員 Console.WriteLine("----------------members----------------------"); foreach(var m in members) { Console.WriteLine(m.Name); } } Console.WriteLine("----------------GetExportedTypes----------------------"); var exportedTypes = assembly.GetExportedTypes(); //獲取程序集中所有的公共類型 foreach(var t in exportedTypes) { Console.WriteLine(t.Name); } Console.WriteLine("----------------GetType----------------------"); var typeName= assembly.GetType("SqlServerHelper");//獲取程序集中指定名稱的類型對象 Console.WriteLine(typeName.Name); } } }
通過ass.CreateInstance(string typeName) 和Activator.CreateInstance(Type t)方法
他們之間的區別
ass.CreateInstance(string typeName) 會動態調用類的無參構造函數創建一個對象,返回值就是創建的對象,如果沒有無參構造函數就會報錯。
Assembly assembly = Assembly.Load("My.Sqlserver.Dal"); object obj = assembly.CreateInstance("My.Sqlserver.Dal.SqlServerHelper"); Console.WriteLine(obj.GetType().ToString());
如果我們來修改SqlServerHelper類的代碼,添加如下構造函數:
public SqlServerHelper(int age) { this.age = age; }
這個時候再來運行創建實例的代碼就會報錯了,而編譯時是不報錯的。
所以我們一般推薦使用Activator.CreateInstance方法來創建反射對象,因為此方法有許多重載,支持將參數傳遞給構造函數。
此時再調用就不會出現異常了。
Type類中有三個用得比較多的方法:
Type類中還有一個IsAbstract屬性:判斷是否為抽象的,包含接口。
它們常用的原因是我們通過反射可以取到的東西太多了,我們需要對數據進行過濾。
添加類BaseSql,讓類SqlServerHelper繼承自BaseSql
然後查看調用代碼:
bool result = typeof(BaseSql).IsAssignableFrom(typeof(SqlServerHelper)); Console.WriteLine(result);
SqlServerHelper _SqlServerHelper = new SqlServerHelper(1); bool result = typeof(SqlServerHelper).IsInstanceOfType(_SqlServerHelper); Console.WriteLine(result);
SqlServerHelper _SqlServerHelper = new SqlServerHelper(1); bool result = typeof(SqlServerHelper).IsSubclassOf(typeof(BaseSql)); Console.WriteLine(result);
項目中常用的利用反射來動態切換數據庫Demo:
新建類庫項目My.Sql.IDal,並添加接口ISqlHelper。通過接口來實現數據庫操作的類的解耦,因為接口是抽象的。
public interface ISqlHelper { string Query(); }
添加類庫項目My.MySql.Dal,並新增類MySqlHelper.cs
My.Sqlserver.Dal、My.MySql.Dal項目分別添加對項目My.Sql.IDal的引用。讓SqlServerHelper繼承自接口ISqlHelper
public class MySqlHelper : ISqlHelper { public string Query() { return this.GetType().ToString(); } } public class SqlServerHelper :ISqlHelper { private int age = 16; public string Name { get; set; } public string Query() { return this.GetType().ToString(); } }
添加App.config配置項
<appSettings> <add key="DBName" value="My.Sqlserver.Dal,SqlServerHelper"/> </appSettings>
ReflectedDemo項目中Program.cs調用代碼:
string str = ConfigurationManager.AppSettings["DBName"]; string strAssembly = str.Split(',')[0]; string strClass=str.Split(',')[1]; Assembly assembly = Assembly.Load(strAssembly); Type t = assembly.GetType(strAssembly + "." + strClass); ISqlHelper obj = Activator.CreateInstance(t) as ISqlHelper; Console.WriteLine(obj.Query());
這樣每次需要切換數據庫時,只要修改配置文件就可以了。
項目結構:
注意:反射雖然很強大,但卻是比較耗性能的,所以一般和緩存結合起來使用。
項目源碼:ReflectedDemo.zip