C#反射與特性(五):類型成員操做

【微信平臺,此文僅受權《NCC 開源社區》訂閱號發佈】c#

前面三篇中,介紹了反射的基本內容和信息對象,反射主要做用於構造函數、屬性、字段、方法、事件等類型成員對象;第四篇介紹了類型的實例化和事件操做。數組

本篇介紹類型的成員操做和實踐練習。緩存

因爲內容較多,多動手實踐一下。微信

目錄5

成員類型

[圖片1 來源:《C# 7.0核心技術指南:19.2 反射並調用成員》]

那麼,如何經過 Type 獲取相應的成員呢?ide

檢索元數據成員

[圖片2 來源:《C# 7.0核心技術指南:19.2 反射並調用成員》]

以上方法具備獲取單個成員或多個成員的版本。函數

全部的 *Info 實例都會在第一次使用時,由反射 API 緩存起來,這種緩存有助於優化 API 的性能。性能

1,MemberInfo

MemberInfo 能夠獲取有關成員屬性的信息,並提供對成員元數據的訪問權限。測試

MemberInfo 類是用於獲取有關類的全部成員(構造函數、事件、字段、方法和屬性)的信息的類的抽象基類。優化

由圖片1能夠看到,MemberInfo 是全部反射類型的基類,此類爲全部成員提供了基本功能。

使用 GetMember()GetMembers() 能夠獲取類型的一個或多個成員。

GetMembers()該方法會返回當前類型(及其基類)的全部公有成員

GetMember 方法能夠經過名稱檢索特定的成員。因爲成員(方法、屬性等)可能會被重載,所以該方法會返回一個數組。

例如

MemberInfo[] members = type.GetMember("test");

1.1 練習-獲取類型的成員以及輸出信息

建立一個類型

public class MyClass
    {
        private static string A { get; set; }
        public string B;
        public string C { get; set; }

        [Required] 
        public int Id { get; set; }
        
        [Phone] 
        public string Phone { get; set; }

        [EmailAddress]
        public string Email { get; set; }

        static MyClass()
        {
            A = "666";
        }

        public MyClass()
        {
            B = "666";
        }

        public MyClass(string message)
        {
            C = message;
        }

        public string Add(string a, string b)
        {
            return a + b;
        }
    }

打印

Type type = typeof(MyClass);
            MemberInfo[] members = type.GetMembers();
            foreach (var item in members)
            {
                Console.WriteLine(item.Name + "    |     " + item.MemberType);
            }

輸出

get_C    |     Method
set_C    |     Method
get_Id    |     Method
set_Id    |     Method
get_Phone    |     Method
set_Phone    |     Method
get_Email    |     Method
set_Email    |     Method
Add    |     Method
GetType    |     Method
ToString    |     Method
Equals    |     Method
GetHashCode    |     Method
.ctor    |     Constructor
.ctor    |     Constructor
C    |     Property
Id    |     Property
Phone    |     Property
Email    |     Property
B    |     Field

1.2 MemberType 枚舉

MemberInfo 中有個 MemberType 枚舉的屬性 名爲 MemberType 。

MemberType 枚舉的定義以下

名稱 說明
All 191 指定全部成員類型
Constructor 1 指定該成員是構造函數
Custom 64 指定該成員是自定義成員類型
Event 2 指定該成員是事件
Field 4 指定該成員是字段
Method 8 指定該成員是方法
NestedType 128 指定該成員是嵌套類型
Property 16 指定該成員是屬性
TypeInfo 32 指定該成員是類型

其中 MemverType.All 的定義以下 All = NestedType | TypeInfo | Property | Method | Field | Event | Constructor

1.3 MemberInfo 獲取成員方法而且調用

下面的例子是經過 GetMembers 獲取到 方法成員,而且傳遞參數調用。

這裏只是示例一下,關於方法的實例化和調用,在本文的第三節。

MemberInfo[] members = type.GetMembers();
            foreach (var item in members)
            {
                // 若是成員屬於方法
                if (item.MemberType == MemberTypes.Method)
                {
                    // 輸出此方法的參數列表:參數類型+參數名稱
                    foreach (ParameterInfo pi in ((MethodInfo)item).GetParameters())
                    {
                        Console.WriteLine("Parameter: Type={0}, Name={1}", pi.ParameterType, pi.Name);
                    }
                    // 若是是方法有兩個參數,則調用
                    if (((MethodInfo)item).GetParameters().Length == 2)
                    {
                        // 調用一個方法以及傳遞參數
                        MethodInfo method = (MethodInfo)item;
                        Console.WriteLine("調用一個方法,輸出結果:");
                        Console.WriteLine(method.Invoke(example, new object[] { "1", "2" }));
                    }
                }
            }

1.4 獲取繼承中方法的信息(DeclaringType 和 ReflectedType)

MemberInfo 中,有三種獲取類型的屬性:

  • MemberType 獲取成員何種函數(例如字段、屬性、方法等);
  • DeclaringType 該屬性返回該成員的定義類型;
  • ReflectedType 返回調用 GetMembers 的具體類型;

由於一個方法能夠繼承,也能夠重寫,那麼不少時候判斷和調用,就須要瞭解相關信息;

DeclaringType :一個類型中使用了父類或者本身的方法,那麼返回此方法的出處;

ReflectedType :從哪一個類型中獲取,就返回哪一個類型;即從個 Type 裏得到成員實例,就返回這個 Type 的名稱;

新建一個兩個類型

/// <summary>
    /// 父類
    /// </summary>
    public class MyClassFather
    {
        /// <summary>
        /// 重寫 ToString()
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return base.ToString();
        }
    }

    /// <summary>
    /// 子類
    /// </summary>
    public class MyClassSon : MyClassFather
    {
    }

控制檯 Program.Main 中,編寫

Type typeFather = typeof(MyClassFather);
            Type typeSon = typeof(MyClassSon);
            Type typeObj = typeof(object);
            Type typeProgram = typeof(Program);

            // 爲了省步驟,就不用 MemberInfo 了
            MethodInfo methodObj = typeObj.GetMethod("ToString");
            MethodInfo methodFather = typeFather.GetMethod("ToString");
            MethodInfo methodSon = typeSon.GetMethod("ToString");
            MethodInfo methodProgram = typeProgram.GetMethod("ToString");

打印 DeclaringType

Console.WriteLine(methodObj.DeclaringType);
            Console.WriteLine(methodFather.DeclaringType);
            Console.WriteLine(methodSon.DeclaringType);
            Console.WriteLine(methodProgram.DeclaringType);

輸出

System.Object
Mytest.MyClassFather
Mytest.MyClassFather
System.Object

解析:

MyClassFather 對 ToString 方法進行了重寫,因此 DeclaringType 獲取到的類型就是 MyClassFather ;

MyClassSon 繼承了 MyClassFather,直接使用父類的 ToString() 方法,因此返回的是 MyClassFather ;

Program 沒有對 ToString() 進行重寫,因此返回的是 Object;

2,從 IL 看反射

筆者的 IL 知識很是薄弱,只能列出一些簡單的內容。

在最前面的練習中,咱們發現

public string C { get; set; }

輸出了

get_C    |     Method
set_C    |     Method
C    |     Property

生成的 IL 是這樣的

.property instance string C()
{  
    .get instance string Mytest.MyClass::get_C()  
    .set instance void Mytest.MyClass::set_C(string)
}

屬性、索引器、事件生成的 IL 總結:

生成的IL

上面三種類型,生成 IL 時,都會有相應的 方法生成,經過 GetMethods() 或者 GetMembers() 能夠獲取到。

2.1 獲取屬性的構造

定義一個類型

public class MyClass
    {
        private string Test;

        public string A
        {
            get { return Test; }
        }

        public string B
        {
            set { Test = value; }
        }

        public string C { get; set; }
    }

從前面的實例中,有很多是獲取屬性列表的示例,可是沒法從中識別出裏面的構造,例如上面的 MyClass 類型。

PropertyInfo 中有個 GetAccessors() 方法,能夠獲取相應的信息。

方法 使用說明
GetAccessors() 返回一個數組,其元素反射了由當前實例反射的屬性的公共 getset 訪問器。
GetAccessors(Boolean) 返回一個數組,其元素反射了當前實例反射的屬性的公共及非公共(若是指定)getset 取值函數。

使用示例

Type type = typeof(MyClass);
            PropertyInfo[] list = type.GetProperties();
            foreach (var item in list)
            {
                var c = item.GetAccessors();
                foreach (var node in c)
                {
                    Console.WriteLine(node);
                }

                Console.WriteLine("************");
            }

輸出

System.String get_A()
************
Void set_B(System.String)
************
System.String get_C()
Void set_C(System.String)
************

若是將上面的屬性 C 改爲

public string C { get; private set; }

那麼只能輸出

System.String get_A()
************
Void set_B(System.String)
************
System.String get_C()
************

若是想獲取私有構造器,可使用.GetAccessors(true)

2.2 屬性的方法

從反射和 IL 咱們得知,一個屬性會自動生成兩個方法。

那麼咱們經過 PropertyInfo 能夠獲取到這些方法。

方法 使用說明
GetSetMethod 獲取 set 方法,返回 MethodInfo
GetGetMethod 獲取 get 方法,返回 MethodInfo
GetAccessors 獲取上面兩個方法的集合,返回 MethodInfo[]

建立一個屬性

public string C { get;  set; }
Type type = typeof(MyClass);
            PropertyInfo property = type.GetProperty("C");
            // 指定獲取 get 或 set
            MethodInfo set = property.GetSetMethod();
            MethodInfo get = property.GetGetMethod();

            MethodInfo[] all = property.GetAccessors();

3,方法操做

咱們要記得,反射,是對元數據的利用;只有實例才能被執行調用。

在這裏,說一下 nameof 關鍵字,nameof 沒有任何做用,他不會對程序產生任何影響。

nameof(T) 能夠輸出 T,例如 namaof(Program) 輸出 Program

那麼什麼狀況下使用到他呢?

咱們在寫代碼時,會使用到例如 Visual Studio 等 IDE,若是使用 nameof,裏面的類型是強類型的,能夠查找引用、跳轉、獲取註釋等。若是須要重構,也能夠快速重命名全部引用。

若是直接使用字符串的話,容易拼錯命名、一旦修改一個命名,須要手動找到全部字符串進行修改。

調用一個實例方法有以下步驟:

步驟 類型 說明
獲取 Type Type 經過程序集等各類方式獲取 Type 類型
獲取實例 object 經過 Activator.CreateInstance(type); 建立實例
獲取方法 MethodInfo或 MemberInfo 經過 Type 獲取對應的方法
設置參數列表 object[] parameters 調用方法時傳遞的參數
執行方法 .Invoke() 方法 執行 MethodInfo.Invoke()
獲取返回結果 object 執行方法獲取到返回結果

3.1 各類方式調用方法

首先咱們定義一個類型

public class MyClass
    {
        /// <summary>
        /// 無參數,五返回值
        /// </summary>
        public void A()
        {
            Console.WriteLine("A被執行");
        }

        /// <summary>
        /// 有參數,有返回值
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        public string B(string left)
        {
            Console.WriteLine("執行 B(string left)");
            return left + "666";
        }

        public string C(string left,int right)
        {
            Console.WriteLine("執行 C(string left,int right)");
            return left + right;
        }

        public string C(string left, string right)
        {
            Console.WriteLine("執行 C(string left, string right)");
            return left + right;
        }
    }

在 Program 編寫代碼獲取到類型的 Type 以及建立實例。

Type type = typeof(MyClass);

            object example = Activator.CreateInstance(type);

3.1.1 調用方法

咱們來調用方法 A()

// 獲取 A
            MethodInfo methodA = type.GetMethod(nameof(MyClass.A));
            
            // 傳遞實例,而且執行實例的 A 方法
            methodA.Invoke(example, new Object[] { });

方法 B 有一個參數,咱們調用時添加參數進去

object result;

            // 獲取 B
            MethodInfo methodB = type.GetMethod(nameof(MyClass.B));

            // 傳遞參數
            // 執行獲取返回結果
            result = methodB.Invoke(example, new[] {"測試"});

3.1.2 獲取參數列表

前面 1.1 中,示例有關於獲取方法參數的代碼。這裏再也不贅述

3.1.3 獲取重載方法

在 《C# 反射與特性》系列的第四篇,咱們介紹了構造函數 ConstructorInfo 的調用和重載,MethodInfo 實際上也是差很少的。

上面咱們使用了 type.GetMethod("方法名稱") 的方法獲取了 MethodInfo ,對於 MyClass.C,有兩個重載,那麼咱們能夠這樣指定要使用的重載方法

// 獲取 C
            // 執行獲取返回結果
            MethodInfo methodC = type.GetMethod(nameof(MyClass.C), new Type[] {typeof(string), typeof(string)});
            result = methodC.Invoke(example, new string[] {"測試", "測試"});
            //       result = methodC.Invoke(example, new Object[] {"測試", "測試"});

至此,對於類型、構造函數、委託、方法的實例化與操做,已經講了一次。

下面將說一下屬性和字段如何設置值和獲取值。

相關文章
相關標籤/搜索