微信平台,此文仅受权《NCC 开源社区》订阅号发布】微信
《C# 反射与特性》已经完成了七篇,讲解了反射的使用和实践应用,第六和第七篇对反射特性等进行了实践总结练习,学习完毕后,能够对通常的实际场景进行应用,解决问题。函数
前面主要考虑入门基础和练习,学习完毕后能够掌握基本知识;本篇是对前面七篇的一些拓展,解决前面遗留的一部分问题,继续研究一些特殊场景下的需求;性能
本篇对一些操做细节进行了补充,介绍了反射的经常使用操做案例和示范,使用另外一种形式进行操做,学习
本系列已经到了第 八 篇,下一篇将主要测算反射各类操做的性能。测试
若是本篇结束,你须要了解的反射操做,本系列尚未介绍到的话,能够联系笔者,在后面的篇章中补上。code
本文的章节较多,建议收藏阅读😄。对象
使用指定的绑定约束和匹配的指定参数列表及区域性来调用指定成员(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 的使用,全是指第一个重载方法。
这一小节介绍 InvokeMember 方法的参数使用以及做用,跟着文章中出现的示例进行操做,将会帮助你更快掌握知识点。
它包含要调用的构造函数、方法、属性或字段成员的名称,注意区分大小写。
invokeAttr参数,是 BindingFlags 枚举,经过 BindingFlags ,咱们能够限定要调用的成员的信息。
例如私有成员用 BindingFlags.NonPublic
、静态成员用BindingFlags.Static
,经过枚举集合来筛选,能够查找到须要使用的成员。
通常为空,不多使用到。笔者也不太清楚。
binder 对象定义一组属性并启用绑定,而绑定可能涉及选择重载方法、强制参数类型和经过反射调用成员。
对其调用指定成员的对象。
若是要调用的是静态对象的成员或实例的静态成员, target 应 null,若是要调用实例成员,则此参数为实例对象。
传递参数,例如方法的参数、属性字段的值等。
若是调用的是方法或者属性字段获取成员值,则会有返回值;若是调用的是 void
方法或者设置属性字段的值。则返回 null
。
枚举值,指定控制绑定以及经过反射执行成员和类型搜索的方式的标记。
下面表格例举了经常使用场景下的枚举,能够用做笔记记录,不须要认真看,须要的时候再回来看。
枚举 | 值 | 说明 |
---|---|---|
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 |
上面的枚举,经过组合,可以筛选出须要的成员。
BindingFlags.Public
以在搜索中包括公共成员。BindingFlags.NonPublic
以在搜索中包括非公共成员(即,私有成员、内部成员和受保护成员)。BindingFlags.FlattenHierarchy
以在层次结构中包含静态成员。如下 BindingFlags 修饰符标志可用于更改搜索的工做方式:
BindingFlags.IgnoreCase
忽略 name
的大小写。BindingFlags.DeclaredOnly
仅搜索类型上声明的成员,而不搜索继承的成员。关于 DeclaredOnly
,能够参考《C#反射与特性(五):类型成员操做》中的 1.4 小节。
如下 BindingFlags 调用标志可用于表示要对成员执行的操做:
CreateInstance
调用构造函数(那么 name
将被忽略,由于构造函数不须要名称);
InvokeMethod
调用方法(不会调用构造函数);
GetField
获取字段的值;
SetField
设置字段的值;
GetProperty
获取属性的值;
SetProperty
设置属性的值;
另外,有些操做可能会有冲突的,例如 InvokeMethod
与 SetField
或 SetProperty
。
若是单独使用 InvokeMethod
,会自动包含 BindingFlags.Public
、BindingFlags.Instance
和 BindingFlags.Static
。这一条很重要。
本节介绍 InvokeMember 的用法以及 MethodInfo 、PropertyInfo 等使用 BindingFlags 的重载方法。
在此以前,建立一个类型
public class MyClass { }
Main 方法中,获取 Type 以及 实例化
Type type = typeof(MyClass); object example = Activator.CreateInstance(type);
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
会报错,为何呢?
给方法传递参数很简单,使用 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);
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
。
经过 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 参数了。
前面七篇忘记了说一下方法参数为 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]}");
之前的篇章以及介绍过实例化类型,直接 Activator.CreateInstance
和 经过构造函数,如今还能够经过 InvokeMember 来实例化类型。
object example = type.InvokeMember("MyClass", BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, null, new object[] { });
BindingFlags.Instance 代表返回的是一个实例,BindingFlags.CreateInstance 代表该操做是实例化类型。
若是构造函数有参数,则 new object[] { }
里面带上参数。
以前呢,咱们经过 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 获取公开的成员。
经过 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
,就能够正常操做了。
访问私有属性,跟私有方法同样简单:
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
,就能够正常操做了。
直接撸码就是了。
建立一个类型
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
,直到找到位置。
上面获取到私有属性的 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; }
以前《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)); }