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

简介: 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));
            }
相关文章
|
1月前
|
编译器 C# 开发者
C# 9.0 新特性解析
C# 9.0 是微软在2020年11月随.NET 5.0发布的重大更新,带来了一系列新特性和改进,如记录类型、初始化器增强、顶级语句、模式匹配增强、目标类型的新表达式、属性模式和空值处理操作符等,旨在提升开发效率和代码可读性。本文将详细介绍这些新特性,并提供代码示例和常见问题解答。
45 7
C# 9.0 新特性解析
|
1月前
|
C# 开发者
C# 10.0 新特性解析
C# 10.0 在性能、可读性和开发效率方面进行了多项增强。本文介绍了文件范围的命名空间、记录结构体、只读结构体、局部函数的递归优化、改进的模式匹配和 lambda 表达式等新特性,并通过代码示例帮助理解这些特性。
36 2
|
3月前
|
编译器 C# Android开发
震惊!Uno Platform 与 C# 最新特性的完美融合,你不可不知的跨平台开发秘籍!
Uno Platform 是一个强大的跨平台应用开发框架,支持 Windows、macOS、iOS、Android 和 WebAssembly,采用 C# 和 XAML 进行编程。C# 作为其核心语言,持续推出新特性,如可空引用类型、异步流、记录类型和顶级语句等,极大地提升了开发效率。要在 Uno Platform 中使用最新 C# 特性,需确保开发环境支持相应版本,并正确配置编译器选项。通过示例展示了如何在 Uno Platform 中应用可空引用类型、异步流、记录类型及顶级语句等功能,帮助开发者更好地构建高效、优质的跨平台应用。
236 59
|
2月前
|
JSON C# 开发者
C#语言新特性深度剖析:提升你的.NET开发效率
【10月更文挑战第15天】C#语言凭借其强大的功能和易用性深受开发者喜爱。随着.NET平台的演进,C#不断引入新特性,如C# 7.0的模式匹配和C# 8.0的异步流,显著提升了开发效率和代码可维护性。本文将深入探讨这些新特性,助力开发者在.NET开发中更高效地利用它们。
42 1
|
2月前
|
XML 存储 缓存
C#使用XML文件的详解及示例
C#使用XML文件的详解及示例
116 0
|
2月前
|
API C#
异步轮询 Web API 的实现与 C# 示例
异步轮询 Web API 的实现与 C# 示例
89 0
|
4月前
|
开发框架 .NET 编译器
总结一下 C# 如何自定义特性 Attribute 并进行应用
总结一下 C# 如何自定义特性 Attribute 并进行应用
114 1
|
4月前
|
数据安全/隐私保护 C# UED
利用 Xamarin 开展企业级移动应用开发:从用户登录到客户管理,全面演示C#与Xamarin.Forms构建跨平台CRM应用的实战技巧与代码示例
【8月更文挑战第31天】利用 Xamarin 进行企业级移动应用开发能显著提升效率并确保高质量和高性能。Xamarin 的跨平台特性使得开发者可以通过单一的 C# 代码库构建 iOS、Android 和 Windows 应用,帮助企业快速推出产品并保持一致的用户体验。本文通过一个简单的 CRM 示例应用演示 Xamarin 的使用方法,并提供了具体的代码示例。该应用包括用户登录、客户列表显示和添加新客户等功能。此外,还介绍了如何增强应用的安全性、数据持久化、性能优化及可扩展性,从而构建出功能全面且体验良好的移动应用。
59 0
|
4月前
|
前端开发 开发者 Apache
揭秘Apache Wicket项目结构:如何打造Web应用的钢铁长城,告别混乱代码!
【8月更文挑战第31天】Apache Wicket凭借其组件化设计深受Java Web开发者青睐。本文详细解析了Wicket项目结构,帮助你构建可维护的大型Web应用。通过示例展示了如何使用Maven管理依赖,并组织页面、组件及业务逻辑,确保代码清晰易懂。Wicket提供的页面继承、组件重用等功能进一步增强了项目的可维护性和扩展性。掌握这些技巧,能够显著提升开发效率,构建更稳定的Web应用。
117 0
|
4月前
|
前端开发 程序员 API
从后端到前端的无缝切换:一名C#程序员如何借助Blazor技术实现全栈开发的梦想——深入解析Blazor框架下的Web应用构建之旅,附带实战代码示例与项目配置技巧揭露
【8月更文挑战第31天】本文通过详细步骤和代码示例,介绍了如何利用 Blazor 构建全栈 Web 应用。从创建新的 Blazor WebAssembly 项目开始,逐步演示了前后端分离的服务架构设计,包括 REST API 的设置及 Blazor 组件的数据展示。通过整合前后端逻辑,C# 开发者能够在统一环境中实现高效且一致的全栈开发。Blazor 的引入不仅简化了 Web 应用开发流程,还为习惯于后端开发的程序员提供了进入前端世界的桥梁。
481 0