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));
            }
相关文章
相关标签/搜索