• C#中BindingFlags的用途
  • 发布于 2个月前
  • 166 热度
    0 评论
在C#编程语言中,提供了一个非常强大的特性:反射(Reflection),让我们可以在运行时动态地获取和操作类型的信息。反射可以用来实现很多高级的功能,比如动态加载程序集,创建对象实例,调用方法,访问字段和属性等。

要使用反射,我们需要使用System.Reflection命名空间中的一些类,比如Type,Assembly,MethodInfo,FieldInfo,PropertyInfo等。这些类都提供了一些方法,可以让我们获取和操作类型的成员(Member),比如构造函数(Constructor),方法(Method),字段(Field),属性(Property),事件(Event)等。

但是,要获取和操作类型的成员,我们不能随意地使用这些方法,而需要指定一个参数:BindingFlags。BindingFlags是一个枚举类型,它定义了一系列的Flag(标志),用来表示我们想要获取或操作哪些类型的成员。

BindingFlags枚举成员包括
Default:表示使用默认的绑定规则,不指定任何特殊的标志。
IgnoreCase:表示忽略成员名称的大小写。
DeclaredOnly:表示只搜索在当前类型中声明的成员,不包括继承的成员。
Instance:表示搜索实例成员,即非静态的成员。
Static:表示搜索静态成员,即类级别的成员。
Public:表示搜索公共成员,即访问修饰符为public的成员。
NonPublic:表示搜索非公共成员,即访问修饰符为internal、protected或private的成员。
FlattenHierarchy:表示在搜索静态成员时,包括从基类继承的公共和受保护的静态成员,但不包括私有的静态成员和嵌套类型。
InvokeMethod:表示要调用一个方法,可以是构造函数、实例方法或静态方法。
CreateInstance:表示要创建一个类型的实例,调用与给定参数匹配的构造函数。
GetField:表示要获取一个字段的值,可以是实例字段或静态字段。
SetField:表示要设置一个字段的值,可以是实例字段或静态字段。
GetProperty:表示要获取一个属性的值,可以是实例属性或静态属性。
SetProperty:表示要设置一个属性的值,可以是实例属性或静态属性。
PutDispProperty:表示要调用一个COM对象上的PROPPUT成员,用于设置一个属性的值。
PutRefDispProperty:表示要调用一个COM对象上的PROPPUTREF成员,用于设置一个引用类型的属性的值。
ExactBinding:表示要求提供的参数类型必须与对应形参类型完全匹配,不允许进行类型转换。
SuppressChangeType:表示禁止进行类型转换,仅在COM互操作中使用。
OptionalParamBinding:表示返回参数数量与提供的参数数量匹配的成员集合,用于处理具有默认值或可变参数的方法。
IgnoreReturn:表示忽略方法的返回值,在COM互操作中使用。
DoNotWrapExceptions:表示不要将反射调用方法时产生的异常包装在TargetInvocationException中。

BindingFlags枚举成员归类分为:
访问修饰符(Access Modifier):表示成员的可见性,有Public,NonPublic两个选项。
成员类型(Member Type):表示成员的种类,有Constructor,Method,Field,Property,Event等多个选项。
继承关系(Inheritance Relation):表示成员是否来自基类或接口,有DeclaredOnly一个选项。
实例或静态(Instance or Static):表示成员是否属于实例或静态,有Instance,Static两个选项。
调用方式(Invoke Method):表示调用方法时是否忽略大小写或抛出异常,有IgnoreCase,ThrowOnUnmappableChar等多个选项。

BindingFlags使用需注意以下几点:
1.BindingFlags是一个位域枚举,可以使用按位或运算符(|)组合多个枚举值,以指定多个绑定条件。

2.在使用反射查找类型成员时,必须指定Instance或Static标志之一,以及Public或NonPublic标志之一,否则将返回空数组或null值。例如,如果只指定BindingFlags.Public,将无法找到任何成员。

3.在使用反射调用成员时,必须指定InvokeMethod、CreateInstance、GetField、SetField、GetProperty或SetProperty标志之一,以表明要执行的操作。如果同时指定了多个操作标志,将抛出AmbiguousMatchException异常。

4.在使用反射调用COM对象的成员时,必须使用PutDispProperty或PutRefDispProperty标志之一,以区分调用PROPPUT或PROPPUTREF成员。这些标志只适用于COM互操作场景,不应用于其他类型的对象。

5.在使用反射调用具有默认值或可变参数的方法时,必须使用OptionalParamBinding标志,并且只能与InvokeMember方法结合使用。此外,还要注意参数的顺序和数量,以匹配正确的方法签名。

6.在使用反射调用方法时,如果提供了自定义的Binder对象,则必须忽略ExactBinding和SuppressChangeType标志,因为这些标志与自定义绑定器的语义冲突。如果没有提供自定义绑定器,则默认绑定器会根据这些标志决定是否允许进行类型转换。

7.在使用反射调用方法时,如果出现了异常,则反射会用TargetInvocationException包装这个异常。如果不想捕获这个包装异常,而是直接抛出原始异常,则可以使用DoNotWrapExceptions标志。

BindingFlags枚举支持其成员值的位运算组合,这意味着我们可以使用按位或运算符(|)来组合多个BindingFlags,以实现更精确的搜索条件。

例如:
// 堆代码 duidaima.com
// 获取公共的实例方法
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
// 获取非公共的静态字段
BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Static;
// 获取声明在当前类中的属性
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
当我们使用反射中的一些方法时,我们需要传入一个BindingFlags参数来指定我们想要获取或操作哪些类型的成员。例如:
// 获取Person类中的所有公共实例属性
Type type = typeof(Person);
PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
// 调用Student类中名为SayHello的非公共静态方法
Type type = typeof(Student);
MethodInfo method = type.GetMethod("SayHello", BindingFlags.NonPublic | BindingFlags.Static);
method.Invoke(null, null);
// 设置Teacher类中名为Name的公共实例字段的值
Type type = typeof(Teacher);
FieldInfo field = type.GetField("Name", BindingFlags.Public | BindingFlags.Instance);
Teacher teacher = new Teacher();
field.SetValue(teacher, "Tom");
我们通一个例子来帮助理解,假设我们有一个类,如下所示:
public class TestClass
{
    public int PublicField = 1;
    private int PrivateField = 2;
    protected int ProtectedField = 3;
    internal int InternalField = 4;
    protected internal int ProtectedInternalField = 5;
    private protected int PrivateProtectedField = 6;

    public void PublicMethod()
    {
        Console.WriteLine("PublicMethod");
    }

    private void PrivateMethod()
    {
        Console.WriteLine("PrivateMethod");
    }

    protected void ProtectedMethod()
    {
        Console.WriteLine("ProtectedMethod");
    }

    internal void InternalMethod()
    {
        Console.WriteLine("InternalMethod");
    }

    protected internal void ProtectedInternalMethod()
    {
        Console.WriteLine("ProtectedInternalMethod");
    }

    private protected void PrivateProtectedMethod()
    {
        Console.WriteLine("PrivateProtectedMethod");
    }
}
这个类有六个字段和六个方法,分别使用了不同的访问修饰符。现在,我们想要使用反射来获取这个类的所有字段和方法,并打印出它们的名称。我们可以使用以下代码来实现:
Type type = typeof(TestClass);
Console.WriteLine("Fields:");
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
{
    Console.WriteLine(field.Name);
}
Console.WriteLine("Methods:");
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
{
    Console.WriteLine(method.Name);
}
我们使用了BindingFlags.Public、BindingFlags.NonPublic、BindingFlags.Instance和BindingFlags.Static四个Flag,以表示我们想要获取所有公共的、非公共的、实例的和静态的字段和方法。运行这段代码,我们可以得到以下输出:
Fields:
PublicField
PrivateField
ProtectedField
InternalField
ProtectedInternalField
PrivateProtectedField
Methods:
PublicMethod
PrivateMethod
ProtectedMethod
InternalMethod
ProtectedInternalMethod
PrivateProtectedMethod
GetType
MemberwiseClone
Finalize
ToString
Equals
GetHashCode
可以看到,除了我们定义的六个字段和六个方法外,还有另外的六个方法也被返回了,它们分别是ToString、Equals、GetHashCode,MemberwiseClone,Finalize和GetType。这是因为这六个方法是从System.Object类继承而来的,而System.Object类是所有.NET类型的基类。由于我们使用了BindingFlags.FlattenHierarchy,所以反射会返回继承自基类的公共静态成员。如果我们不想要返回这些继承成员,我们可以使用BindingFlags.DeclaredOnly枚举值,以表示只返回在TestClass类中声明的成员。修改后的代码如下:
Type type = typeof(TestClass);
Console.WriteLine("Fields:");
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
    Console.WriteLine(field.Name);
}
Console.WriteLine("Methods:");
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
    Console.WriteLine(method.Name);
}
运行这段代码,我们可以得到以下输出:
Fields:
PublicField
PrivateField
ProtectedField
InternalField
ProtectedInternalField
PrivateProtectedField
Methods:
PublicMethod
PrivateMethod
ProtectedMethod
InternalMethod
ProtectedInternalMethod
PrivateProtectedMethod
这样就只返回了TestClass类中定义的字段和方法,不包括继承自基类的成员。

BindingFlags是一个非常重要的枚举类型,它可以让我们灵活地使用反射来获取和操作类的成员。通过了解BindingFlags的用法和注意事项,我们可以更好地利用反射这个强大的特性,实现更多的功能。
用户评论