微信平台,此文仅授权《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
设置属性的值;
另外,有些操作可能会有冲突的,例如 InvokeMethod
与 SetField
或 SetProperty
。
如果单独使用 InvokeMethod
,会自动包含 BindingFlags.Public
、BindingFlags.Instance
和 BindingFlags.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));
}