在前面簡單學習了Linq To Object的常用標准查詢運算符,在我們項目開發中,運用Linq表達式方便了我們編程,而接下來要講的另一個內容——反射,也能很好地幫助我們處理某些特殊的情況。
一、反射的概念:
反射提供了封裝程序集、模塊和類型的對象(Type類型)。可以使用反射動態創建類型的實例,將類型綁定到現有對象,或從現有對象獲取類型並調用其方法或訪問其字段和屬性。如果代碼中使用了屬性,可以利用反射對它們進行訪問。
一般使用:工廠類,通過反射創建類的實例,實現層與層之間的解耦: 數據層→數據會話層→業務邏輯層。 其中,數據會話層通過反射創建數據層的實例,業務邏輯層調用。
二、反射Type中的四個函數:
Person person = new Person(); Student student = new Student(); //判斷指定的兩個成員是否存在繼承關系(判斷一個類是否可用賦值給另一個類) --后者繼承於前者 bool b1 = typeof(Person).IsAssignableFrom(typeof(Student)); //student繼承了person bool b3 = person.GetType().IsAssignableFrom(student.GetType());//另一種寫法 bool b2 = typeof(Itest).IsAssignableFrom(typeof(Teacher)); //判斷是否是指定類的實例 bool b4 = typeof(Person).IsInstanceOfType(student); //結果為true student繼承了person bool b5 = person.GetType().IsInstanceOfType(student);//另一種寫法 GetType當前對象的實例 //判斷是否是某個類的子類,非接口 bool b6 = typeof(Student).IsSubclassOf(typeof(Person)); //判斷是否是抽象 if (typeof(Drive).IsAbstract) { Console.WriteLine("是抽象的"); } else { Console.WriteLine("不是抽象的"); } Console.ReadKey();
要使用到的類和接口:
class Person{} class Student:Person{} class Teacher:Itest{} interface Itest{} public abstract class Drive{}
三、反射中常用類的使用:
需求:通過反射獲得Common程序集中的成員,並使用成員。
Comman程序集:
public class FileCommon { public string Name { get; set; } public int Age { get; set; } public char Gender { get; set; } public FileCommon(string name) { this.Name = name; } public void WriteData(string path,string content) { File.WriteAllText(path, content, Encoding.Default); } }
1、把Common.dll放到該應用程序的bin/Debug目錄下
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"Common.dll"); Assembly ass = Assembly.LoadFile(path); //需要絕對路徑 LoadFile加載路徑程序集內容 -->也不一定要在debug目錄下,自己構建絕對路徑也可 Type[] types = ass.GetTypes(); //獲得加載到的程序集中的所有數據類型,包括公開和不公開的
//Type[] types = ass.GetExportedTypes(); //獲取加載到的公開程序集中的數據
2、可以遍歷程序集中的類型,獲取類型的命名空間和類型名稱
foreach (Type item in types) { Console.WriteLine(item.Name); //類型名稱 --也就是common下類或者接口的名稱 Console.WriteLine(item.Namespace); //命名空間 }
3、獲取指定程序集中的數據類型
//獲得指定程序集中數據類型,包括公開的和不公開的 Type type = ass.GetType("Common.FileCommon"); Console.WriteLine(type.Name); Console.WriteLine(type.Namespace);
4、創建type對象
a:創建沒有構造函數的對象
object o = ass.CreateInstance("Common.FileCommon"); //FileCommom為Common命名空間的一個類 → 命名空間.類名
b:反射出來的類型有構造函數
object o2 = Activator.CreateInstance(type, "參數");
注意: 當反射出來的類型如果有構造函數,用上面代碼中 ass.CreateInstance則會出現錯誤,如果有構造函數,那么該如何知道構造函數的參數?
ConstructorInfo [] info = type.GetConstructors(); //查詢所有的構造函數,可以看到構造函數需要傳遞參數的參數類型
5、獲得數據類型中所有的屬性
//獲得數據類型中所有的屬性 PropertyInfo[] pinfo = type.GetProperties(); //獲取屬性 然后可以遍歷 foreach (PropertyInfo item in pinfo) { Console.WriteLine(item.Name); }
6、獲得數據類型中所有的函數
//獲得數據類型中所有的方法函數 MethodInfo[] minfo = type.GetMethods(); //獲取所有的函數 foreach (MethodInfo item in minfo) { Console.WriteLine(item.Name); }
7、目的:調用函數
//調用WriteData函數 MethodInfo method = type.GetMethod("WriteData"); //該類中的writeData方法 object o3 = method.Invoke(o2, new object[] { "2.txt", "通過反射調用噠" }); Console.WriteLine("調用成功"); Console.ReadKey();
四、小結:
當獲得一個 .dll程序集的時候,需要先獲取所有的類型,也就是這個程序集中的類,然后根據所需要類的名稱,去創建指定名稱的對象,如:Type type = ass.GetType("Common.FileCommon"); ,然后利用type獲取構造函數、所有函數、屬性(還有一些可以 type.方法名來獲取相應的需求),再選擇創建type對象的方法(構造或無構造),然后根據獲得的函數來調用,傳入相應的參數。
五、具體應用例子:
使用反射制作關機插件
1、先定義關機接口和具體關機的插件方法:
public interface IPlugin { string Name { get; } //負責關閉計算機 void GuanJi(int t); }
public class ShutDownClass : IPlugin { public void GuanJi(int t) { //關機 ShutDown(t.ToString()); //返回一個K類型的默認值 } public void ShutDown(string second) { Process process = new Process(); process.StartInfo.FileName = "cmd.exe"; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardInput = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.CreateNoWindow = true; process.Start(); process.StandardInput.WriteLine("shutdown -s -f -t " + second); process.StandardInput.WriteLine("exit"); process.Close(); process.Dispose(); } public string Name { get { return "關機"; } } }
2、窗體調用:
private void Form1_Load(object sender, EventArgs e) { string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plug"); //bin/debug/plug存放.dll文件 string[] files = Directory.GetFiles(path); foreach (string item in files) { Assembly ass = Assembly.LoadFile(item); Type[] types = ass.GetExportedTypes(); for (int i = 0; i < types.Length; i++) { //判斷是否為接口中的類,並不是抽象類 if (typeof(IPlugin).IsAssignableFrom(types[i]) && !types[i].IsAbstract) { //創建對象 o = Activator.CreateInstance(types[i]); //獲得指定對象 PropertyInfo pif = types[i].GetProperty("Name"); object o2 = pif.GetValue(o); //添加到菜單欄 ToolStripItem tsi = PluginToolStripMenuItem.DropDownItems.Add(o2.ToString()); //把數據對象存儲到要使用的對象 tsi.Tag = types[i]; //給添加的tsi注冊單擊事件 tsi.Click += (s, e2) => { Type t = tsi.Tag as Type; MethodInfo mi = t.GetMethod("GuanJi"); mi.Invoke(o, new object[] { 3000 }); }; } } } }
六、總結:
很多.dll文件可以通過反射的方式來獲取相應的類,類的方法屬性的使用,一些應用程序也可以通過反編譯軟件來獲取一些方法屬性等。