(轉)C#之玩轉反射

來源:互聯網
上載者:User

標籤:blog   http   io   ar   os   使用   sp   for   strong   

前言

之所以要寫這篇關於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是所有類的基類。),細心的讀者一定會發現這裡的輸出並沒有包含privateprotected存取權限的成員。這就應了上面的那句話: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_test1Test2私人保護類型,

是不可以擷取到它們的的,但是我們通過反射卻可以,具體的代碼如下所示:

 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         }

 

可以看到我執行個體化了這個類,並且設定了Test33,下面我通過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),下面我們繼續看最終輸出的結果:

是不是現在感覺可以為所欲為了?但是還沒有完。

 

深入窺視屬性

因為屬性存在getset,並且兩者都是方法,所以比較棘手。我們需要通過屬性對象擷取getset方法,在通過調用他們才達到修改這個屬性的值。

比如下面的代碼:

 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                 else21                 {22                     MethodBody mbody = finfo.GetMethodBody();23                     Console.WriteLine("沒有參數的方法名:{0}  傳回值類型:{1}",24                         finfo.Name,25                         finfo.ReturnType.ToString());26                 }27             }28             Console.ReadKey();29         }

 在這裡我進行了一些簡單的判斷比如判斷輸入參數的數量以及類型,如果不進行這些判斷就會導致程式無法繼續執行,具體為什麼可以看下的輸出結果,你就能明白筆者為什麼要這麼做了。

 

下面就是具體的結果:

讀者一定發現了這其中還有getset,你可能會認為它們不是屬性嗎?怎麼跑到方法這裡來了,其實上面我已經說了。這些其實也是方法。這也是為什麼上面我需要去判斷輸入參數的數量以及類型的緣故。

 

(轉)C#之玩轉反射

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.