c#學習系列之反射
前期準備
在VS2012中新建一個控制臺應用程序(我的命名是ReflectionStudy),這個項目是基於.net 4.0。接著我們打開Program.cs文件,按照如下在Program中寫一個我們自己的類:
1 public class RefClass 2 { 3 private int _test3; 4 private int _test1 { get; set; } 5 protected int Test2 { get; set; } 6 public int Test3 { get; set; } 7 8 public void Show() 9 { 10 11 } 12 }
窺視內部
常言道知彼知己百戰不殆,所以我們第一步也是關鍵的一步就是要窺視RefClass類的結構(這裏我們假設對RefClass並不理解)。
首先我們先要縱覽全局才能繼續深入,所以我們先在Main中寫入如下代碼:
1 static void Main(string[] args) 2 { 3 Type t = typeof(RefClass); 4 MemberInfo[] minfos = t.GetMembers(); 5 foreach (MemberInfo minfo in minfos) 6 { 7 Console.WriteLine(minfo.Name); 8 } 9 Console.ReadKey(); 10 }
在這裏我們獲取這個類的類型,然後獲取了其中的公共成員(可能很多人都會認為GetMembers是獲取全部,但其實只是獲取公開的所有成員。)然後我們通過foreach將所有的成員的名稱循環輸出。
然後我們可以查看控制臺的輸出:
在這裏我們可以看到其中不僅僅輸出了我們所寫類中的成員,同時還輸出了父類的成員(如果不理解的這裏幫你們補充下基礎,Object是所有類的基類。),細心的讀者一定會發現這裏的輸出並沒有包含private和protected訪問權限的成員。這就應了上面的那句話:GetMembers默認返回公開的成員。
僅僅只能看到這些公開的成員對我們來說意義並不大,所以我們需要查看到那些非公有的成員。
下面我們將上面的代碼改成如下所示:
1 static void Main(string[] args)
2 {
3 Type t = typeof(RefClass);
4 MemberInfo[] minfos = t.GetMembers(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public );
5 foreach (MemberInfo minfo in minfos)
6 {
7 Console.WriteLine(minfo.Name);
8 }
9 Console.ReadKey();
10 }
從中我們看到我們使用了GetMembers的重載版本,並且傳入了枚舉類型,分別是“包含非公開”、“包含實例成員”和“包含公開”。然後我們就可以獲取到所有成員了。
最終我們將會得出下面這些成員:
到這裏你可能會認為我們已經檢索結束了,但是你有沒有發現屬性很多,而且還包含了大量的父類中的屬性,假設我們只關註該類中的成員,並不關註父類中的成員該如何做呢?
其實我們只需要加上一個枚舉類型(BindingFlags.DeclaredOnly):
1 MemberInfo[] minfos = t.GetMembers(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly );
然後我們再查看結果:
此時就只包含該類中的成員了。
下面我們在RefClass類中添加兩個靜態方法,如下所示:
1 public class RefClass
2 {
3 private int _test3;
4 private int _test1 { get; set; }
5 protected int Test2 { get; set; }
6 public int Test3 { get; set; }
7
8 private static void Show2()
9 {
10 }
11
12 public static void Show3()
13 {
14 }
15
16 public void Show()
17 {
18
19 }
20 }
然後我們繼續查看,可以發現最終的結果並沒有輸出這些靜態成員。這個時候我們只需要在GetMembers中加上一個枚舉:BindingFlags.Static即可。
這裏我們僅僅輸出了所有的成員,但是卻沒有區分出是方法還是屬性所以我們在Main中添加一個方法:
1 static void Main(string[] args)
2 {
3 Type t = typeof(RefClass);
4 Func<MemberTypes, String> getType = (x) =>
5 {
6 switch (x)
7 {
8 case MemberTypes.Field:
9 {
10 return "字段";
11 }
12 case MemberTypes.Method:
13 {
14 return "方法";
15 }
16 case MemberTypes.Property:
17 {
18 return "屬性";
19 }
20 default:
21 {
22 return "未知";
23 }
24 }
25 };
26 MemberInfo[] minfos = t.GetMembers(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static );
27 foreach (MemberInfo minfo in minfos)
28 {
29 Console.WriteLine(minfo.Name + ";類型:" + getType(minfo.MemberType));
30 }
31 Console.ReadKey();
32 }
這裏我用了一個局部方法來根據類型輸出對應的文本,因為篇幅的原因我就只判斷了幾個基本的類型。
最終輸出的結果如下:
到此為止我們已經能夠窺視整個結構。
深入窺視字段
通過上面的內容我們僅僅縱覽了全局,下面我們將要繼續深入,首先我們先拿字段下手。
這裏我們不在使用GetMembers而需要使用GetFields(當然跟GetMembers一樣如果不傳入指定的枚舉只返回公開的字段),代碼如下所示:
1 static void Main(string[] args)
2 {
3 Type t = typeof(RefClass);
4 FieldInfo[] finfos = t.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
5 foreach (FieldInfo finfo in finfos)
6 {
7 Console.WriteLine("字段名稱:{0} 字段類型:{1} ", finfo.Name, finfo.FieldType.ToString());
8 }
9 Console.ReadKey();
10 }
最終的輸出結果如下所示:
一直到這裏大家都會認為我們僅僅只是分析,感覺沒有什麽實質的東西,下面就來點實質的東西,你可以看到_test3、_test1和Test2是私有和保護類型,
是不可以獲取到它們的值的,但是我們通過反射卻可以,具體的代碼如下所示:
1 static void Main(string[] args)
2 {
3 Type t = typeof(RefClass);
4 RefClass rc = new RefClass();
5 rc.Test3 = 3;
6 FieldInfo[] finfos = t.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
7 foreach (FieldInfo finfo in finfos)
8 {
9 Console.WriteLine("字段名稱:{0} 字段類型:{1} rc中的值為:{2}", finfo.Name, finfo.FieldType.ToString(), finfo.GetValue(rc));
10 }
11 Console.ReadKey();
12 }
可以看到我實例化了這個類,並且設置了Test3為3,下面我通過finfo.GetValue輸出了這個值,結果如下圖:
現在是不是感覺有點酷了?這還沒完呢,我們光獲取不算什麽,下面我們還要修改它的值:
1 static void Main(string[] args)
2 {
3 Type t = typeof(RefClass);
4 RefClass rc = new RefClass();
5 rc.Test3 = 3;
6 FieldInfo[] finfos = t.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
7 foreach (FieldInfo finfo in finfos)
8 {
9 finfo.SetValue(rc, 100);
10 Console.WriteLine("字段名稱:{0} 字段類型:{1} rc中的值為:{2}", finfo.Name, finfo.FieldType.ToString(), finfo.GetValue(rc));
11 }
12 Console.ReadKey();
13 }
這裏我只是在foreach中增加了一條語句finfo.SetValue(rc,100),下面我們繼續看最終輸出的結果:
是不是現在感覺可以為所欲為了?但是還沒有完。
深入窺視屬性
因為屬性存在get和set,並且兩者都是方法,所以比較棘手。我們需要通過屬性對象獲取get和set方法,在通過調用他們才達到修改這個屬性的值。
比如下面的代碼:
1 static void Main(string[] args)
2 {
3 Type t = typeof(RefClass);
4 RefClass rc = new RefClass();
5 rc.Test3 = 3;
6 PropertyInfo[] finfos = t.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
7 foreach (PropertyInfo finfo in finfos)
8 {
9 MethodInfo getinfo = finfo.GetGetMethod(true);
10 Console.WriteLine("get方法的名稱{0} 返回值類型:{1} 參數數量:{2} MSIL代碼長度:{3} 局部變量數量:{4}", getinfo.Name, getinfo.ReturnType.ToString(),
11 getinfo.GetParameters().Count(),
12 getinfo.GetMethodBody().GetILAsByteArray().Length,
13 getinfo.GetMethodBody().LocalVariables.Count);
14
15 MethodInfo setinfo = finfo.GetSetMethod(true);
16 Console.WriteLine("get方法的名稱{0} 返回值類型:{1} 參數數量:{2} MSIL代碼長度:{3} 局部變量數量:{4}", setinfo.Name, setinfo.ReturnType.ToString(),
17 setinfo.GetParameters().Count(),
18 setinfo.GetMethodBody().GetILAsByteArray().Length,
19 setinfo.GetMethodBody().LocalVariables.Count);
20
21 setinfo.Invoke(rc, new object[] { 123 });
22 object obj = getinfo.Invoke(rc, null);
23 Console.WriteLine("方法名:{0} 內部值:{1}", finfo.Name, obj);
24 }
25 Console.ReadKey();
26 }
這裏我們循環每個屬性,通過GetGetMethod獲取get方法(調用該方法時如果傳入true則無法獲取非公開的get方法set也是一樣),接著我們輸出了該方法的返回類型和參數數量和MSIL代碼長度以及局部變量的數量,
當然你如果有興趣可以繼續分析輸入參數以及局部變量等,這裏由於篇幅的緣故就不能介紹太多了。最後我們調用了set方法將值改變,然後再通過調用get方法獲取這個屬性的值。
最終的結果如下所示:
深入窺視方法
首先我們需要將RefClass修改成如下所示:
1 public class RefClass
2 {
3 private int _test3;
4 private int _test1 { get; set; }
5 protected int Test2 { get; set; }
6 public int Test3 { get; set; }
7
8 private static void Show2()
9 {
10
11 }
12
13 public static string Show3(string s)
14 {
15 int b;
16 int c;
17 return s;
18 }
19
20 public string Show(string s)
21 {
22 string a;
23 return s;
24 }
25 }
主要是在方法中增加局部變量並且加上返回值,避免最後輸出的時候沒有值。其實這裏的方法跟屬性部分類似,但是為了能夠完整的描述所有,所以筆者依然會講解一遍。
下面我們直接上代碼:
1 static void Main(string[] args)
2 {
3 Type t = typeof(RefClass);
4 RefClass rc = new RefClass();
5 rc.Test3 = 3;
6 MethodInfo[] finfos = t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Static );
7 foreach (MethodInfo finfo in finfos)
8 {
9 if (finfo.GetParameters().Count() > 0 && finfo.GetParameters()[0].ParameterType == typeof(string) )
10 {
11 object obj = finfo.Invoke(rc, new[] { "123" });
12 MethodBody mbody = finfo.GetMethodBody();
13 Console.WriteLine("擁有參數的方法名:{0} 返回值類型:{1} 參數1類型:{2} 參數1名稱:{3} 方法調用後返回的值:{4}",
14 finfo.Name,
15 finfo.ReturnType.ToString(),
16 finfo.GetParameters()[0].ParameterType.ToString(),
17 finfo.GetParameters()[0].Name,
18 obj.ToString());
19 }
20 else
21 {
22 MethodBody mbody = finfo.GetMethodBody();
23 Console.WriteLine("沒有參數的方法名:{0} 返回值類型:{1}",
24 finfo.Name,
25 finfo.ReturnType.ToString());
26 }
27 }
28 Console.ReadKey();
29 }
在這裏我進行了一些簡單的判斷比如判斷輸入參數的數量以及類型,如果不進行這些判斷就會導致程序無法繼續執行,具體為什麽可以看下的輸出結果,你就能明白筆者為什麽要這麽做了。
下面就是具體的結果:
讀者一定發現了這其中還有get和set,你可能會認為它們不是屬性嗎?怎麽跑到方法這裏來了,其實上面我已經說了。這些其實也是方法。這也是為什麽上面我需要去判斷輸入參數的數量以及類型的緣故。
c#學習系列之反射