反射用於在程序運行過程中,獲取類里面的信息或發現程序集並運行的一個過程。通過反射可以獲得.dll和.exe后綴的程序集里面的信息。使用反射可以看到一個程序集內部的類,接口,字段,屬性,方法,特性等信息。
一、各種GetType()、typeof的區別
首先就是獲取Tyoe對象的來源不同:
class Program { static void Main(string[] args) { Type t1 = Type.GetType("ConsoleApplication2.Person"); //從字符串中獲得Type對象 Console.WriteLine(t1.ToString()); Type t2 = typeof(ConsoleApplication2.Person); //從具體類中獲得Type對象 Console.WriteLine(t2.ToString()); Person p = new Person(); Type t3 = p.GetType(); //實例,從實例中獲得Type對象 Assembly ass = Assembly.LoadFrom(@"C:\Users\Administrator\Desktop\ConsoleApplication2\ConsoleApplication2\bin\Debug\ConsoleApplication2.exe"); Console.WriteLine(ass.GetType("ConsoleApplication2.Person").ToString()); //從字符串中獲得Type對象 Module mod = ass.GetModules()[0]; Console.WriteLine(mod.GetType("ConsoleApplication2.Person").ToString()); //從字符串中獲得Type對象 Console.ReadKey(); } }
三者的區別在於typeof()和Type.GetType()是從一個類中獲取對象,而Object.GetType()是從一個類的實例獲得對象。
而前兩者的區別在於:
- 只有typeof()是運算符。
- Type.GetType()是實例方法。
- Object.GetType()是基類System.Object的方法(無參數,在Type類、Assembly類、Module類中都有這個無參方法),實例方法。
- Assembly.GetType() 、Module.GetType()、Type.Get()都是各自對象的實例方法。
在System.Reflection命名空間內包含多個反射常用的類,下面表格列出了常用的幾個類。
類型 | 作用 |
Assembly | 通過此類可以加載操縱一個程序集,並獲取程序集內部信息 |
EventInfo | 該類保存給定的事件信息 |
FieldInfo | 該類保存給定的字段信息 |
MethodInfo | 該類保存給定的方法信息 |
MemberInfo | 該類是一個基類,它定義了EventInfo、FieldInfo、MethodInfo、PropertyInfo的多個公用行為 |
Module | 該類可以使你能訪問多個程序集中的給定模塊 |
ParameterInfo | 該類保存給定的參數信息 |
PropertyInfo | 該類保存給定的屬性信息 |
二、System.Reflection.Assembly類
通過Assembly可以動態加載程序集,並查看程序集的內部信息,其中最常用的就是Load()這個方法。
Assembly assembly = Assembly.Load("MyAssembly");
注意在Assembly里面的加載程序集有3個方法,分別是Load、LoadFrom和LoadFile。這3個方法有什么異同呢?
1、如果你引用了命名空間,那么就直接Load()方法,參數里面寫上命名空間+類名就可以加載了。
2、如果僅僅知道一個dll文件的那么就要用LoadFrom()方法了,參數里面直接填寫完整的路徑。
LoadFrom 方法具有以下缺點。請考慮改用 Load。
-如果已加載一個具有相同標識的程序集,則即使指定了不同的路徑,LoadFrom 仍返回已加載的程序集。
-如果用 LoadFrom 加載一個程序集,隨后加載上下文中的一個程序集嘗試加載具有相同顯示名稱的程序集,則加載嘗試將失敗。對程序集進行反序列化時,可能發生這種情況。
總結: LoadFrom只能用於加載不同標識的程序集, 也就是唯一的程序集, 不能用於加載標識相同但路徑不同的程序集。
3、LoadFile (加載指定路徑上的程序集文件的內容。)
這個方法是從指定的文件來加載程序集,它是調用外部的API實現的加載方式,和上面Load,LoadFrom方法的不同之處是這個方法不會加載此程序集引用的其他程序集,也就是不會加載程序的依賴項。而同時也是不能加載相同標識的程序集的。
利用Assembly的object CreateInstance(string)方法可以反射創建一個對象,參數0為類名。
class Program { static void Main(string[] args) { Assembly assm = Assembly.Load("fanshe"); Console.WriteLine(assm.FullName); //輸出 fanshe, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null //注釋上面兩行,移除程序集的引用 Assembly assm1 = Assembly.LoadFrom(@"D:\fanshe.dll"); Console.WriteLine(assm1.FullName); ////輸出 fanshe, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null //與Assembly.LoadFrom基本一樣,只是如果被加載的dll,還依賴其他的dll的話,被依賴的對象不會加載 Assembly assm2 = Assembly.LoadFile(@"D:\fanshe.dll"); Console.WriteLine(assm2.FullName); Console.ReadKey(); } }
三、System.Type類
Type是最常用到的類,它一般用於裝載反射得到的類對象,通過Type可以得到一個類的內部信息,也可以通過它反射創建一個對象。一般有三個常用的方法可以得到Type對象。
1.利用typeof()得到Type對象
Type type = typeof(Example);
2.利用System.Object.GetType()得到Type對象
Example example = new Example();
Type type = example.GetType();
3.利用System.Type.GetType()得到Type對象
Type type = Type.GetType("MyAssembly.Example",false,true) //注意0是類名,參數1表示若找不到對應類時是否拋出異常,參數2表示類名是否區分大小寫
示例:
public class Program { static void Main(string[] args) { Person p1 = new Person(); Type t1 = typeof(Person); Type t2 = p1.GetType(); Person p2 = Activator.CreateInstance(t1) as Person; Person p3 = Activator.CreateInstance(t2) as Person; Console.ReadKey(); } } public class Person { public int Id { get; set; } public string Name { get; set; } }
四、反射方法
1.通過 Type.GetMethods()能查找到類里面的方法
代碼示例:
class Program { static void Main(string[] args) { Type t = typeof(Person); MethodInfo[] MethodInfoList = t.GetMethods(); foreach (MethodInfo info in MethodInfoList) { Console.WriteLine(info); } Console.ReadKey(); } }
輸出結果如下:
留意到里面所有的方法,包括繼承來的都列出來了。另外可以留意到,屬性的讀取設置,在根本上也是一個方法。
調用反射得到的方法使用Invoke方法(),示例如下:
static void Main(string[] args) { Assembly assembly = Assembly.Load("fanshe"); Type type = assembly.GetType("fanshe.Person"); //注意要輸入全部路徑,包括命名空間 object obj = Activator.CreateInstance(type); MethodInfo methodShow = type.GetMethod("Show"); //根據方法名獲取MethodInfo對象 //調用無參方法Show,Invoke表示執行方法 methodShow.Invoke(obj, null); // 參數1類型為object[],代表Hello World方法的對應參數,輸入值為null代表沒有參數 Console.ReadKey(); }
五、反射屬性
1、通過System.Reflection.Property能查找到類里面的屬性
常用的方法有GetValue(object,object[])獲取屬性值和SetValue(object,object,object[])設置屬性值
代碼示例:
class Program { static void Main(string[] args) { Type t = typeof(Person); PropertyInfo[] PropertyInfoList = t.GetProperties(); foreach (PropertyInfo info in PropertyInfoList) { Console.WriteLine(info); } Console.ReadKey(); } }
輸出結果如下:
2、另外還可以通過PropertyInfo對象的GetValue和SetValue方法讀取和設置創建出來的對象的屬性值
class Program { static void Main(string[] args) { Assembly assembly = Assembly.Load("fanshe"); Type type = assembly.GetType("fanshe.Person"); //注意要輸入全部路徑,包括命名空間 object obj = Activator.CreateInstance(type); //調用要屬性的方法 PropertyInfo propertyName = type.GetProperty("Name"); //獲取Name屬性對象 propertyName.SetValue(obj, "張飛", null); //設置Name屬性的值 object objName = propertyName.GetValue(obj, null); //獲取屬性值 Console.WriteLine(objName); //輸出張飛 Console.ReadKey(); } }
下面給出一個方法與屬性的綜合示例:
class Program { static void Main(string[] args) { Assembly assembly = Assembly.Load("fanshe"); Type type = assembly.GetType("fanshe.Person"); //注意要輸入全部路徑,包括命名空間 object obj = Activator.CreateInstance(type); MethodInfo methodShow = type.GetMethod("Show"); //根據方法名獲取MethodInfo對象 //調用無參方法Show,Invoke表示執行方法 methodShow.Invoke(obj, null); // 參數1類型為object[],代表Hello World方法的對應參數,輸入值為null代表沒有參數 //調用帶參數方法 MethodInfo methodAdd = type.GetMethod("Add"); object[] objArr = new object[]{1,2}; methodAdd.Invoke(obj,objArr); //輸出3 第二個參數為傳入去的參數列表 //調用要屬性的方法 PropertyInfo propertyName = type.GetProperty("Name"); //獲取Name屬性對象 propertyName.SetValue(obj,"張飛",null); //設置Name屬性的值 PropertyInfo propertyAge = type.GetProperty("Age"); //獲取Age屬性對象 propertyAge.SetValue(obj, 24, null); //把Age屬性設置為34 MethodInfo methodSay = type.GetMethod("Say"); methodSay.Invoke(obj,null); //輸出我的名字叫張飛,我今年24歲
Console.ReadKey(); } }
Person類的代碼如下:
namespace fanshe { public class Person { private string name; public string Name get { return name; } set { name = value; } } private int age; public int Age { get { return age; } set { age = value; } } public void Show() { Console.WriteLine("我是Person類里的Show方法!"); } public void Say() { Console.WriteLine("我的名字叫{0},我今年{1}歲", this.Name, this.Age); } public void Add(int i, int j) { Console.WriteLine(i + j); } } }
2、根據屬性的類型設置屬性的值:
我們有可能會一次過設置很多屬性的值,而這些屬性里面可能有字符串類型、整型等等。因此,我們需要動態設置,這是需要根據屬性的類型設置屬性的值。
namespace DynamicSetValue { class Program { static void Main(string[] args) { Type type = typeof(Person); //注意要輸入全部路徑,包括命名空間 object obj = Activator.CreateInstance(type); //假設這是存在於XML的數據 Dictionary<string, string> dic = new Dictionary<string, string>(); dic.Add("Id", "1"); dic.Add("Name", "神靈武士"); dic.Add("Birthday", "2001-01-01"); PropertyInfo[] ProArr = type.GetProperties(); foreach (PropertyInfo p in ProArr) { if (dic.Keys.Contains(p.Name)) { p.SetValue(obj, Convert.ChangeType(dic[p.Name], p.PropertyType), null); //當需要給屬性設置不同類型的值時 } } Person person = obj as Person; Console.WriteLine(person.Birthday); Console.ReadKey(); } } //有三種不同類型的屬性 public class Person { public int Id { get; set; } public string Name { get; set;} public DateTime Birthday { get; set; } } }
輸出如下:
如果不根據類型來轉換,則報如下錯誤:
六、反射字段
通過 System.Reflection.FieldInfo 能查找到類里面的字段
它包括有兩個常用方法SetValue(object ,object )和GetValue(object) 因為使用方法與反射屬性非常相似。
class Program { static void Main(string[] args) { Assembly assembly = Assembly.Load("fanshe"); Type type = assembly.GetType("fanshe.Person"); //注意要輸入全部路徑,包括命名空間 object obj = Activator.CreateInstance(type); //調用要屬性的方法 FieldInfo fieldName = type.GetField("name", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance); //獲取name字段對象,后面的那個枚舉參數指定,非公開字段也搜索,即讀取private的name字段 fieldName.SetValue(obj, "張飛"); //設置name字段的值 object objName = fieldName.GetValue(obj); //獲取屬性值 Console.WriteLine(objName); //輸出張飛 Console.ReadKey(); } }
七、反射特性
通過System.Reflection.MemberInfo的GetCustomAttributes(Type,bool)就可反射出一個類里面的特性。
class Program { static void Main(string[] args) { Assembly assembly = Assembly.Load("fanshe"); Type type = assembly.GetType("fanshe.Person"); //注意要輸入全部路徑,包括命名空間 object obj = Activator.CreateInstance(type); object[] typeAttributes=type.GetCustomAttributes(false); //獲取Person類的特性 foreach (object attribute in typeAttributes) { Console.WriteLine(attribute.ToString()); //輸出 System.SerializableAttribute 因為我在Person上里加了個[Serializable] } Console.ReadKey(); } }
八、應用實例
利用反射實現的簡單工廠模式的多數據庫系統實例:
App.Config配置文件
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="DAL" value="ConsoleApplication2.Access"/> </appSettings> </configuration>
主程序代碼:
namespace ConsoleApplication2 { class Program { static void Main(string[] args) { Assembly assembly = Assembly.Load("ConsoleApplication2"); string str = System.Configuration.ConfigurationManager.AppSettings["DAL"]; Type type = assembly.GetType(str); //注意真正實現的對象路徑寫在了外面,這樣要更改數據庫只要改配置文件,不需要改動程序! IDAL DAL = (IDAL)Activator.CreateInstance(type); DAL.Insert(); Console.ReadKey(); } } interface IDAL { void Insert(); } class SqlServer : IDAL { public void Insert() { Console.WriteLine("SqlServer增加一條記錄!"); } } class Access : IDAL { public void Insert() { Console.WriteLine("Access增加一條記錄!"); } } }
2、不引用命名空間根據路徑運行dll里的方法示例
Person類代碼:
namespace fanshe { public class Person { private string name; public string Name { get { return name; } { name = value; } } private int age; public int Age { get { return age; } set { age = value; } } public string Say(string str) { Console.WriteLine("我的名字叫{0},我今年{1}歲,另外你給我傳入的參數是:{2}", this.Name, this.Age,str); return "返回結果"; } } }
主程序代碼:
class Program { static void Main(string[] args) { //先把引用的命名空間移除 Assembly assembly = Assembly.LoadFrom(@"D:\fanshe.dll"); Type type = assembly.GetType("fanshe.Person"); object obj = Activator.CreateInstance(type); PropertyInfo proName = type.GetProperty("Name"); proName.SetValue(obj,"關羽",null); PropertyInfo proAge = type.GetProperty("Age"); proAge.SetValue(obj,24,null); MethodInfo methodSay = type.GetMethod("Say"); object[] objList = new object[1]{"傳入測試參數"}; object result = methodSay.Invoke(obj,objList); Console.WriteLine(result); //輸出 返回值 Console.ReadKey(); } }
3、獲得List<T>中的T類型:
List<Dog> dogs = new List<Dog>(); Type type = dogs.GetType(); if (type.IsGenericType) { Type[] genericArgTypes = type.GetGenericArguments(); if (genericArgTypes[0] == typeof(Dog)) { //你想要判斷的是這個嗎? } }
當然,如果List<T>是你定義的泛型,那么直接typeof(T)更加簡單。