1. 程式人生 > 其它 >C#進階學習4--反射(Reflection)

C#進階學習4--反射(Reflection)

一.反射的定義

審查元資料並收集關於它的型別資訊的能力。

二.基礎概念

(1)Assembly:定義和載入程式集,載入在程式集中的所有模組以及從此程式集中查詢型別並建立該型別的例項。
(2)Module:獲取包含模組的程式集以及模組中的類等,還可以獲取在模組上定義的所有全域性方法或其他特定的非全域性方法。
(3)ConstructorInfo:獲取建構函式的名稱、引數、訪問修飾符(如pulic 或private)和實現詳細資訊(如abstract或virtual)等。
(4)MethodInfo(GetMethod/GetMethods):獲取方法的名稱、返回型別、引數、訪問修飾符(如pulic 或private)和實現詳細資訊(如abstract或virtual)等。
(5)FiedInfo(GetField/GetFields):獲取欄位的名稱、訪問修飾符(如public或private)和實現詳細資訊(如static)等,並獲取或設定欄位值。
(6)EventInfo(GetEvent/GetEvents):獲取事件的名稱、事件處理程式資料型別、自定義屬性、宣告型別和反射型別等,新增或移除事件處理程式。
(7)PropertyInfo(GetProperty/GetProperties):獲取屬性的名稱、資料型別、宣告型別、反射型別和只讀或可寫狀態等,獲取或設定屬性值。
(8)ParameterInfo:獲取引數的名稱、資料型別、是輸入引數還是輸出引數,以及引數在方法簽名中的位置等。
(9)MemberInfo(GetMember/GetMembers):獲取欄位、事件、屬性等各種資訊

三.反射作用

在演示反射的作用之前,我們先定義如下實體類,假設該實體類位於一個第三方的類庫下,類庫名稱為“TestClass”,類名為"Person"

    public class Person
    {
        private int id;
        public int Id { get => id; set => id = value; }
        public string Name { set; get; }
        public string Phone { get; set; }      
        public Person()
        {
           
        }
        public Person(string a, int b)
        {
            this.Name = a;
            this.Phone = b.ToString();
        }
        public Person(int id, string name, string phone)
        {
            this.Id = id;
            this.Name = name;
            this.Phone = phone;
        }        
        public string getName()
        {
            return this.Name;            
        }
        public string getName1(string str)
        {
            return str;
        }       
        public string getPhone()
        {
            return this.Phone;
        }
        public string getPhone(string str)
        {
            return this.Phone+str;
        }
        public string getPhone(string str,int num)
        {
            return this.Phone + str+num;
        }
        private void privateMethod(string a)
        {
            Console.WriteLine("這是一個私有方法,傳入的引數是:"+a);
        }  
    }

1.建立不帶引數的物件

建立不帶成熟的物件,本質是就是呼叫無參的建構函式,具體實現如下

        /// <summary>
        /// 建立不帶引數的物件
        /// </summary>
        /// <returns></returns>
        private static Person CreateWithoutParms()
        {
            Assembly assembly = Assembly.Load("TestClass");//載入程式集
            Type type = assembly.GetType("TestClass.Person");//獲取類名稱(要帶上名稱空間)
            object o = Activator.CreateInstance(type);//建立Person實體,無參構造
            Person person = o as Person;
            return person;
        }

在控制檯中呼叫

            Person person = CreateWithoutParms();
            person.Name = "張三";
            Console.WriteLine("person.Name:"+ person.Name);

返回結果如下:
成功呼叫了建立了Person,並呼叫了Person的無參構造方法

2.建立帶引數的物件

建立帶成熟的物件,本質是就是呼叫帶引數的建構函式,具體實現如下

        /// <summary>
        /// 建立帶引數的物件
        /// </summary>
        /// <returns></returns>
        private static Person CreateWithParms()
        {
            Assembly assembly = Assembly.Load("TestClass");//載入程式集
            Type type = assembly.GetType("TestClass.Person");//獲取類名稱(要帶上名稱空間)
            object o = Activator.CreateInstance(type, new object[] {"a",666 });//建立Person實體,有參構造
            Person person = o as Person;
            return person;
        }

在控制檯中呼叫

            Person person = CreateWithParms();
            Console.WriteLine("person.Name:"+person.Name+ " person.Phone:" + person.Phone);

返回結果如下:
成功呼叫了建立了Person,並利用帶引數的構造直接給屬性賦值

說明:如果建構函式為私有的,可以在建立例項時,將CreateInstance中的nonPublic引數設定為true,即可使用私有的建構函式建立例項

            object o = Activator.CreateInstance(type,true);

3.呼叫公共方法

利用反射呼叫第三方類的方法,可以通過反射得到對應的物件之後,利用得到的物件來執行物件中的方法,但是在這裡,主要講解通過反射,直接呼叫第三方類中的方法,具體實現如下

        /// <summary>
        /// 呼叫帶引數的方法(無過載)
        /// </summary>
        /// <returns></returns>
        private static string CallFunction()
        {
            Assembly assembly= Assembly.Load("TestClass");
            Type type = assembly.GetType("TestClass.Person");
            object o = Activator.CreateInstance(type);
            MethodInfo methodInfo = type.GetMethod("getName1");
            string result=methodInfo.Invoke(o, new object[] { "這是傳入引數" }).ToString();
            return result;
        }

在控制檯中呼叫

            string rsult = CallFunction();
            Console.WriteLine(rsult);

返回結果如下:
在這裡我們看到,利用反射成功呼叫了getName1方法,需要注意的是,getName1方法並沒有任何過載,如果需要呼叫帶有過載的方法,需要用下面的方法,這裡我們假設需要呼叫getPhone(string str,int num)方法

        private static string CallFunctionWithOverload()
        {
            Assembly assembly = Assembly.Load("TestClass");
            Type type = assembly.GetType("TestClass.Person");
            object o = Activator.CreateInstance(type);
            MethodInfo methodInfo = type.GetMethod("getPhone", new Type[] { typeof(string), typeof(int) });//在這裡需要把引數型別陣列傳遞給GetMethod方法
            string result=methodInfo.Invoke(o, new object[] { "這是傳入的String引數", 666 }).ToString();
            return result;
        }

在控制檯中呼叫

            string rsult = CallFunctionWithOverload();
            Console.WriteLine(rsult);

返回結果如下:

通過以上的例子,我們可以看到,呼叫有過載和無過載方法的關鍵,就是在GetMethod中是否傳遞引數的型別。

下面寫一個綜合的例子,呼叫Person類中的所有方法,並輸出結果,如果引數型別為String,則預設傳"AAA",如果引數型別為Int,則預設傳666,實現方法如下:

        private static void CallAllFunction()
        {
            Assembly assembly = Assembly.Load("TestClass");
            Type type = assembly.GetType("TestClass.Person");
            object o = Activator.CreateInstance(type);
            foreach (MethodInfo methodInfoItem in type.GetMethods())
            {
                Console.WriteLine("執行"+ methodInfoItem.Name+ "方法");
                List<object> objectList = new List<object>();
                foreach (ParameterInfo parameterInfoItem in methodInfoItem.GetParameters())
                {
                    if (parameterInfoItem.ParameterType == typeof(String))
                    {
                        objectList.Add("AAA");
                    }
                    else if (parameterInfoItem.ParameterType == typeof(int))
                    {
                        objectList.Add(666);
                    }
                }
                try//這裡使用try...catch...是為了簡化處理屬性獲取失敗導致程式報錯問題
                {
                    string result = methodInfoItem.Invoke(o, objectList.ToArray()).ToString();
                    Console.WriteLine("結果為:" + result);
                }
                catch 
                {
                }
            }
        }

呼叫後返回結果如下:

在這裡我們看到,Person中的方法已經全部執行,包括所有的系統方法

4.呼叫私有方法

        /// <summary>
        /// 呼叫私有方法
        /// </summary>
        private static void CallPrivateFunction()
        {
            Assembly assembly = Assembly.Load("TestClass");
            Type type = assembly.GetType("TestClass.Person");
            object o = Activator.CreateInstance(type);
            MethodInfo methodInfo = type.GetMethod("privateMethod", BindingFlags.Instance | BindingFlags.NonPublic);
            methodInfo.Invoke(o, new object[] { "張三"});
        }

呼叫後返回結果如下:

通過以上例子,我們不難發現,呼叫公共方法與私有方法的區別就是在呼叫type的GetMethod方法時,是否設定"BindingFlags.Instance | BindingFlags.NonPublic"

5.獲取與操作屬性

        /// <summary>
        /// 獲取與操作屬性
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="propertyValue"></param>
        private static void getAndSetProperity(string propertyName,string propertyValue)
        {
            //1.通過反射建立Person實體
            Assembly assembly = Assembly.Load("TestClass");
            Type type = assembly.GetType("TestClass.Person");
            object o = Activator.CreateInstance(type, new object[] { "張三", 131000000 });
            PropertyInfo propertyInfo=type.GetProperty(propertyName);
            Console.WriteLine("修改前Phone:"+ propertyInfo.GetValue(o));//獲取屬性值
            propertyInfo.SetValue(o, propertyValue);//設定屬性值
            Console.WriteLine("修改後Phone:" + propertyInfo.GetValue(o));           
        }

呼叫後返回結果如下:

通過以上例子,可以發現,獲取與設定屬性的關鍵方法分別為GetValue與SetValue,關鍵傳入引數為通過反射得到的實體類

6.獲取與操作欄位

        /// <summary>
        /// 獲取與操作欄位
        /// </summary>
        /// <param name="fieldName"></param>
        /// <param name="fieldValue"></param>
        private static void getAndSetField(string fieldName, int fieldValue)
        {
            Assembly assembly = Assembly.Load("TestClass");
            Type type = assembly.GetType("TestClass.Person");
            object o = Activator.CreateInstance(type, new object[] {1, "張三", "131000000" });            
            FieldInfo fieldInfo = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
            Console.WriteLine("修改前id"+ fieldInfo.GetValue(o));
            fieldInfo.SetValue(o, fieldValue);
            Console.WriteLine("修改後id" + fieldInfo.GetValue(o));
        }

呼叫後返回結果如下:
設定和操作欄位的方法與設定和操作屬性的方法基本一直,需要注意的是,在用type的GetField方法時,如果獲取或設定的是私有欄位,需要設定該方法的可訪問屬性,本例中的設定為"BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance"


接下來,我們繼續研究反射在泛型中的作用,在進一步研究之前,我們先定義如下泛型類,同以上實體類一樣,假設該泛型類位於一個第三方的類庫下,類庫名稱為“TestClass”,類名為"GenericClass"

    public class GenericClass<X,Y,Z>
    {
        public X xxx{ set; get; }
        public Y yyy { set; get; }
        public Z zzz { set; get; }
        public void PrintParm<A,B,C>(A a, B b, C c)
        {
            Console.WriteLine("A的型別為" + a.GetType().Name + ",值為" + a.ToString());
            Console.WriteLine("B的型別為" + b.GetType().Name + ",值為" + b.ToString());
            Console.WriteLine("C的型別為" + c.GetType().Name + ",值為" + c.ToString());
        }
    }

7.建立泛型類並呼叫

        /// <summary>
        /// 呼叫泛型類中的方法
        /// </summary>
        private static void GenericWithParms()
        {
            Assembly assembly = Assembly.Load("TestClass"); 
            Type type = assembly.GetType("TestClass.GenericClass`3"); 
            type= type.MakeGenericType(new Type[] { typeof(string),typeof(int),typeof(DateTime)});
            object o = Activator.CreateInstance(type);
            MethodInfo methodInfo = type.GetMethod("PrintParm");
            methodInfo = methodInfo.MakeGenericMethod(new Type[] { typeof(string), typeof(int), typeof(DateTime) });
            methodInfo.Invoke(o, new object[] {"張三",666,DateTime.Now});
        }

呼叫後返回結果如下:
針對以上程式碼,做出以下幾點說明:
1).
只有在建立泛型類時,才需要做這樣的設定,數字為泛型類總引數的個數
2).
在建立泛型實體之前,要通過type的MakeGenericType方法,設定傳入的引數型別
3).
同建立泛型類一樣,在呼叫泛型方法前,也需要設定泛型方法的引數型別
4).如果呼叫的是泛型類中的普通方法,無需設定泛型方法的引數型別,反之,如果呼叫的是普通類中的泛型方法,無需設定泛型類引數個數,也無需設定引數型別

至此,反射的常用方式講解完畢...