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


微信平台,此文僅授權《NCC 開源社區》訂閱號發布】

《C# 反射與特性》已經完成了七篇,講解了反射的使用和實踐應用,第六和第七篇對反射特性等進行了實踐總結練習,學習完畢后,可以對一般的實際場景進行應用,解決問題。

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

本篇對一些操作細節進行了補充,介紹了反射的常用操作案例和示范,使用另一種形式進行操作,

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

如果本篇結束,你需要了解的反射操作,本系列還沒有介紹到的話,可以聯系筆者,在后面的篇章中補上。

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

1,InvokeMember

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

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

前面我們使用 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));
            }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM