C#反射與特性(八):反射操做的示例大全

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

《C# 反射與特性》已經完成了七篇,講解了反射的使用和實踐應用,第六和第七篇對反射特性等進行了實踐總結練習,學習完畢後,能夠對通常的實際場景進行應用,解決問題。函數

前面主要考慮入門基礎和練習,學習完畢後能夠掌握基本知識;本篇是對前面七篇的一些拓展,解決前面遺留的一部分問題,繼續研究一些特殊場景下的需求;性能

本篇對一些操做細節進行了補充,介紹了反射的經常使用操做案例和示範,使用另外一種形式進行操做,學習

本系列已經到了第 八 篇,下一篇將主要測算反射各類操做的性能。測試

若是本篇結束,你須要瞭解的反射操做,本系列尚未介紹到的話,能夠聯繫筆者,在後面的篇章中補上。code

本文的章節較多,建議收藏閱讀😄。對象

1,InvokeMember

使用指定的綁定約束和匹配的指定參數列表及區域性來調用指定成員(CultureInfo)。blog

這個方法的定義有點晦澀難懂,沒事,不須要理會,繼續向下閱讀。繼承

前面咱們使用 MemberInfo 來獲取類型的成員並進行操做,也使用了 PropertyInfo 、MethodInfo 等,咱們使用到的成員,都是公開成員。

InvokeMember 方法可讓咱們便捷地調用靜態對象或實例對象的成員, 包括私有成員、索引器等。

InvokeMember 有主要有兩個重載:

public object? InvokeMember(string name, BindingFlags invokeAttr, Binder? binder, object? target, object?[]? args);
public object? InvokeMember(string name, BindingFlags invokeAttr, Binder? binder, object? target, object?[]? args, CultureInfo? culture);

注:不能使用 InvokeMember 調用泛型方法。

InvokeMember 中的參數比較複雜,咱們通常只使用第一個重載方法,第二個重載方法無非多了個 CultureInfo,用來處理語言文化差異,本篇中關於 InvokeMember 的使用,全是指第一個重載方法。

1.1 InvokeMember 參數

這一小節介紹 InvokeMember 方法的參數使用以及做用,跟着文章中出現的示例進行操做,將會幫助你更快掌握知識點。

1.1.1 name

它包含要調用的構造函數、方法、屬性或字段成員的名稱,注意區分大小寫。

1.1.2 invokeAttr

invokeAttr參數,是 BindingFlags 枚舉,經過 BindingFlags ,咱們能夠限定要調用的成員的信息。

例如私有成員用 BindingFlags.NonPublic 、靜態成員用BindingFlags.Static ,經過枚舉集合來篩選,能夠查找到須要使用的成員。

1.1.3 binder

通常爲空,不多使用到。筆者也不太清楚。

binder 對象定義一組屬性並啓用綁定,而綁定可能涉及選擇重載方法、強制參數類型和經過反射調用成員。

1.1.4 target

對其調用指定成員的對象。

若是要調用的是靜態對象的成員或實例的靜態成員, target 應 null,若是要調用實例成員,則此參數爲實例對象。

1.1.5 args

傳遞參數,例如方法的參數、屬性字段的值等。

1.1.6 返回

若是調用的是方法或者屬性字段獲取成員值,則會有返回值;若是調用的是 void 方法或者設置屬性字段的值。則返回 null

1.1.7 BindingFlags

枚舉值,指定控制綁定以及經過反射執行成員和類型搜索的方式的標記。

下面表格例舉了經常使用場景下的枚舉,能夠用做筆記記錄,不須要認真看,須要的時候再回來看。

枚舉 說明
CreateInstance 512 指定反射應建立指定類型的實例
DeclaredOnly 2 指定只應考慮在所提供類型的層次結構級別上聲明的成員
Default 0 指定未定義任何綁定標誌
FlattenHierarchy 64 指定應返回層次結構往上的公共成員和受保護靜態成員。 不返回繼承類中的私有靜態成員。 靜態成員包括字段、方法、事件和屬性。 不支持嵌套類型。
GetField 1024 獲取字段的值
GetProperty 4096 獲取屬性的值
IgnoreCase 1 指定在綁定時不該考慮成員名稱的大小寫
IgnoreReturn 16777216 在 COM 互操做中用於指定能夠忽略成員的返回值
Instance 4 獲取的是實例成員
InvokeMethod 256 調用方法
NonPublic 32 獲取的是非公開成員
Public 16 獲取的是公開成員
SetField 2048 給字段賦值
SetProperty 8192 給屬性賦值
Static 8 獲取的是靜態成員
SuppressChangeType 131072 未實現

根據枚舉的影響做用分類:

可訪問性標識 綁定參數標識 操做成員標識
DeclaredOnly ExactBinding CreateInstance
FlattenHierarchy OptionalParamBinding GetField
IgnoreCase SetField
IgnoreReturn GetProperty
Instance SetProperty
NonPublic InvokeMethod
Public PutDispProperty
Static PutRefDispProperty

上面的枚舉,經過組合,可以篩選出須要的成員。

1.1.8 根據是否公開

  • 指定 BindingFlags.Public 以在搜索中包括公共成員。
  • 指定 BindingFlags.NonPublic 以在搜索中包括非公共成員(即,私有成員、內部成員和受保護成員)。
  • 指定 BindingFlags.FlattenHierarchy 以在層次結構中包含靜態成員。

1.1.9 大小寫和搜索層次

如下 BindingFlags 修飾符標誌可用於更改搜索的工做方式:

  • BindingFlags.IgnoreCase 忽略 name的大小寫。
  • BindingFlags.DeclaredOnly 僅搜索類型上聲明的成員,而不搜索繼承的成員。

關於 DeclaredOnly ,能夠參考《C#反射與特性(五):類型成員操做》中的 1.4 小節。

1.1.10 指定對成員進行何種操做

如下 BindingFlags 調用標誌可用於表示要對成員執行的操做:

  • CreateInstance 調用構造函數(那麼 name 將被忽略,由於構造函數不須要名稱);

  • InvokeMethod 調用方法(不會調用構造函數);

  • GetField 獲取字段的值;

  • SetField 設置字段的值;

  • GetProperty 獲取屬性的值;

  • SetProperty 設置屬性的值;

另外,有些操做可能會有衝突的,例如 InvokeMethodSetFieldSetProperty

若是單獨使用 InvokeMethod ,會自動包含 BindingFlags.PublicBindingFlags.InstanceBindingFlags.Static 。這一條很重要。

1.2 實踐使用 InvokeMember 和成員的重載方法

本節介紹 InvokeMember 的用法以及 MethodInfo 、PropertyInfo 等使用 BindingFlags 的重載方法。

在此以前,建立一個類型

public class MyClass
    {

    }

Main 方法中,獲取 Type 以及 實例化

Type type = typeof(MyClass);
            object example = Activator.CreateInstance(type);

1.2.1 靜態方法和實例方法

Myclass 中增長兩個方法,一個靜態方法,一個實例方法:

public static void A()
        {
            Console.WriteLine("A()方法被調用");
        }
        public void B()
        {
            Console.WriteLine("B()方法被調用");
        }

經過 InvokeMember 調用

type.InvokeMember("A", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, new object[] { });

            type.InvokeMember("B", BindingFlags.InvokeMethod, null, example, new object[] { });

            type.GetMethod("A").Invoke(null, null);

            type.GetMethod("B").Invoke(example, null);

第一個調用靜態方法 A,第二個調用實例方法 B,第三第四個則是使用 MethodInfo 執行方法。

若是方法沒有參數的話,可使用 new object[] { },也可使用 null

InvokeMember 方式調用方法的話,靜態方法使用 BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static;實例方法使用 BindingFlags.InvokeMethod

可是若是對實例方法使用 BindingFlags.InvokeMethod | BindingFlags.Public 會報錯,爲何呢?

1.2.2 方法參數

給方法傳遞參數很簡單,使用 new object[] { } 便可。

例如

public void Test(string a, string b)
        {
            Console.WriteLine(a + b);
        }
Type type = typeof(MyClass);
            object example = Activator.CreateInstance(type);

            type.InvokeMember("Test", BindingFlags.InvokeMethod, null, example, new object[] { "666","666" });

還可使用指定命名對於參數的方式去調用方法。

示例以下

// 正常實例化調用
            (new MyClass()).Test(b: "前", a: "後");

            // 參數的值
            var parmA = new object[] { "前", "後" };
            // 指定參數的名稱
            var parmB = new string[] { "b", "a" };

            type.InvokeMember("Test", BindingFlags.InvokeMethod, null, example, parmA, null, null, parmB);

1.2.3 字段屬性

BindingFlags 中

  • GetField 獲取字段的值;
  • SetField 設置字段的值;
  • GetProperty 獲取屬性的值;
  • SetProperty 設置屬性的值;

MyClass 中,增長如下代碼

public string C = "c";
        public string D { get; set; }

Main 中使用

type.InvokeMember("C",BindingFlags.SetField,null,example,new object[] { "666"});
            Console.WriteLine(type.InvokeMember("C", BindingFlags.GetField ,null, example, null));

            type.InvokeMember("D", BindingFlags.SetProperty, null, example, new object[] { "666" });
            Console.WriteLine(type.InvokeMember("D", BindingFlags.GetProperty, null, example, null));

若是不肯定是屬性仍是方法,可使用 BindingFlags.GetField | BindingFlags.GetProperty

1.2.4 默認成員

經過 DefaultMemberAttribute 特性標記一個類中的默認成員,可使用 BindingFlags.Default 來調用。

[DefaultMemberAttribute("TestA")]
    public class MyClass
    {
        public void TestA(string a, string b)
        {
            Console.WriteLine(a + b);
        }
        public void TestB(string a, string b)
        {
            Console.WriteLine(a);
        }
    }
Type type = typeof(MyClass);
            object example = Activator.CreateInstance(type);

            type.InvokeMember(string.Empty, BindingFlags.InvokeMethod | BindingFlags.Default, null, example, new object[] { "666", "666" });

此時,不須要傳遞 name 參數了。

1.2.5 方法的 ref、out 參數

前面七篇忘記了說一下方法參數爲 ref、out 的狀況,如今補上。

當參數是 ref 或者 out 時,能夠這樣調用 MethodInfo。

使用方法是:不須要任何特殊的屬性,能夠直接調用。

public void Test(ref string a, ref string b)
        {
            Console.WriteLine($"交互前,a={a},b={b}");
            string c = a;
            b = a;
            a = c;
        }
Type type = typeof(MyClass);
            object example = Activator.CreateInstance(type);

            string[] list = new string[] { "1", "2" };
            MethodInfo method = type.GetMethod("Test");
            method.Invoke(example, list);
            Console.WriteLine($"交換後,a={list[0]},b={list[1]}");

1.2.6 建立實例

之前的篇章以及介紹過實例化類型,直接 Activator.CreateInstance 和 經過構造函數,如今還能夠經過 InvokeMember 來實例化類型。

object example = type.InvokeMember("MyClass", BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, null, new object[] { });

BindingFlags.Instance 代表返回的是一個實例,BindingFlags.CreateInstance 代表該操做是實例化類型。

若是構造函數有參數,則 new object[] { } 裏面帶上參數。

1.2.7 訪問成員

以前呢,咱們經過 GetMembers() 方法獲取類型的全部成員,以前使用到的方法是無參數的重載。
有一個使用了 BindingFlags 的重載方法以下:

public abstract MemberInfo[] GetMembers(BindingFlags bindingAttr);

經過 BindingFlags ,咱們能夠獲取到特定的成員。

Type type = typeof(List<int>);
            MemberInfo[] memInfo = type.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);

            for (int i = 0; i < memInfo.Length; i++)
            {
                Console.WriteLine(memInfo[i].Name);
            }

上面的 BindingFlags ,BindingFlags.DeclaredOnly 獲取在當前類型定義的成員(非繼承的成員)、BindingFlags.Instance 獲取到實例(即有返回結果)、BindingFlags.Public 獲取公開的成員。

1.2.8 調用私有方法

經過 BindingFlags ,咱們能夠很方便的訪問類型的私有方法並執行。

public class MyClass
    {
        private string Test(string a, string b)
        {
            return a + b;
        }
        private void WriteLine(string message)
        {
            Console.WriteLine(message);
        }
    }
Type type = typeof(MyClass);
            object example = Activator.CreateInstance(type);
            MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.NonPublic);

            for (int i = 0; i < methods.Length; i++)
            {
                Console.WriteLine(methods[i].Name);
            }

            MethodInfo method = methods.FirstOrDefault(x => x.Name == "WriteLine");
            method.Invoke(example, new object[] { "打印輸出" });

上面的參數中指定獲取類型的公開和非公開成員方法,而且是在當前類型中定義的成員(排查繼承的成員,例如 ToString() 方法等),而且返回了實例。

不管是公開方法仍是私有方法,只要拿到 MethodInfo,就能夠正常操做了。

1.2.9 私有屬性

訪問私有屬性,跟私有方法同樣簡單:

public class MyClass
    {
        /// <summary>
        /// 這樣的屬性沒有任何意義
        /// </summary>
        private string A { get; set; }

        /// <summary>
        /// 這樣的屬性會報錯
        /// </summary>
        // private string B{ get; private set; }
        
        public string C { get; private set; }
    }
Type type = typeof(MyClass);
            object example = Activator.CreateInstance(type);

            PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance);
            foreach (var item in properties)
            {
                Console.WriteLine(item.Name);
            }

            PropertyInfo property = properties.FirstOrDefault(x=>x.Name=="A");
            property.SetValue(example,"666");
            Console.WriteLine(property.GetValue(example));

            property = properties.FirstOrDefault(x=>x.Name=="C");
            property.SetValue(example,"777");
            Console.WriteLine(property.GetValue(example));

不管是公開屬性仍是私有屬性,仍是私有構造器,只要拿到 MethodInfo,就能夠正常操做了。

1.2.10 父類的私有屬性

直接擼碼就是了。

建立一個類型

public class A
    {
        private string Test { get; set; }
    }
    public class B : A
    {
    }
    public class C : B
    {

    }
Type type = typeof(C);
            PropertyInfo property;
            object example;

            while (true)
            {
                Console.WriteLine($"查找{type.Name}");
                property = type.GetProperties(
                    BindingFlags.NonPublic |
                    BindingFlags.Instance)
                    .FirstOrDefault(x => x.Name == "Test");
                // 已經找到
                if (property != null)
                {
                    example = Activator.CreateInstance(type);
                    break;
                }
                // 往上一層查找
                if (type.BaseType == null)
                    throw new NullReferenceException("找不到呀");

                type = type.BaseType;
            }

            property.SetValue(example, "設置屬性值");
            Console.WriteLine(property.GetValue(example));

上面的循環會不斷的向上查找屬性 Test,直到找到位置。

1.2.11 屬性的 GetGetMethod() 和 SetGetMethod()

上面獲取到私有屬性的 PropertyInfo 後,經過 SetValue 設置值和 GetValue 獲取值。

經過 GetGetMethod()SetGetMethod() 也能夠實現上面的操做。

原理是編譯屬性時會生成兩個方法。

public class MyClass
    {
        private string Test { get; set; }
    }
Type type = typeof(MyClass);
            object example = Activator.CreateInstance(type);

            // GetProperty() 會報錯,拿不到屬性
            // type.GetProperty("Test", BindingFlags.DeclaredOnly |BindingFlags.NonPublic |BindingFlags.Instance);

            // 獲取到私有屬性
            PropertyInfo property = type.GetProperties(
                BindingFlags.DeclaredOnly |
                BindingFlags.Public |
                BindingFlags.NonPublic |
                BindingFlags.Instance)
                .FirstOrDefault(x => x.Name == "Test");

            // nonPublic: true 獲取私有方法
            MethodInfo set = property.GetSetMethod(nonPublic: true);
            set.Invoke(example, new object[] { "測試" });

            MethodInfo get = property.GetGetMethod(nonPublic:true);
            // 獲取屬性值
            Console.WriteLine(get.Invoke(example, null));
            // 獲取屬性值
            Console.WriteLine(property.GetValue(example));

由於 GetGetMethod()SetGetMethod() 獲取到方法後,經過 Invoke 調用委託,聽說性能比較高。

固然,把上面的屬性改爲下面這樣,照樣成立。

public string Test { get;private set; }

1.2.12 GetAccessors

以前《C#反射與特性(五):類型成員操做》2.2 章節已經介紹過這個方法,如今讓咱們來經過 GetAccessors() 完成屬性讀值設置值的操做。

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

拿到全部的屬性

Type type = typeof(MyClass);
            object example = Activator.CreateInstance(type);

            // GetProperty() 會報錯,拿不到屬性
            // type.GetProperty("Test", BindingFlags.DeclaredOnly |BindingFlags.NonPublic |BindingFlags.Instance);

            // 獲取到私有屬性
            PropertyInfo[] properties = type.GetProperties(
                BindingFlags.DeclaredOnly |
                BindingFlags.Public |
                BindingFlags.NonPublic |
                BindingFlags.Instance);

開始操做

// 循環全部的屬性而且調用構造方法

            foreach (var item in properties)
            {
                MethodInfo[] methods = item.GetAccessors(nonPublic: true);

                // Set 方法,Get 方法
                MethodInfo mSet = null;
                MethodInfo mGet = null;

                Console.WriteLine("\n屬性   " + item.Name);

                // 其實一個屬性就兩個方法,不須要使用 foreach 的
                foreach (var itemNode in methods)
                {
                    // 沒有返回值,說明就是 Void set_B(System.String) 這樣的方法咯
                    // 即 set 構造器
                    if (itemNode.ReturnType == typeof(void))
                    {
                        Console.WriteLine("set 構造器    " + itemNode);
                        Console.WriteLine("是否公有    " + itemNode.IsPublic);
                        mSet = itemNode;
                    }
                    else
                    {
                        Console.WriteLine("get 構造器    " + itemNode);
                        Console.WriteLine("是否公有    " + itemNode.IsPublic);
                        mGet = itemNode;
                    }
                }
                // 賦值,讀值
                mSet.Invoke(example, new object[] { "設置值" });
                Console.WriteLine("獲取到值      " + mGet.Invoke(example, null));
            }
相關文章
相關標籤/搜索