BindingFlags in C#: Core enumeration types for reflection

The C# programming language provides a very powerful feature: reflection, which allows us to dynamically obtain and operate type information at runtime. Reflection can be used to implement many advanced functions, such as dynamically loading assemblies, creating object instances, calling methods, accessing fields and properties, etc.

To use reflection, we need to use some classes in the System.Reflection namespace, such as Type, Assembly, MethodInfo, FieldInfo, PropertyInfo, etc. These classes provide some methods that allow us to obtain and operate members of the type, such as constructors, methods, fields, properties, events, etc. Would you like to pay a little attention

However, to obtain and operate members of a type, we cannot use these methods arbitrarily, but need to specify a parameter: BindingFlags. BindingFlags is an enumeration type that defines a series of Flags to indicate which types of members we want to obtain or operate.

BindingFlags enumeration members include:

  • Default: Indicates using the default binding rules without specifying any special flags.

  • IgnoreCase: Indicates that the case of member names is ignored.

  • DeclaredOnly: Indicates that only members declared in the current type are searched, excluding inherited members.

  • Instance: Indicates searching for instance members, that is, non-static members.

  • Static: means searching for static members, that is, class-level members.

  • Public: Indicates searching for public members, that is, members whose access modifier is public.

  • NonPublic: means searching for non-public members, that is, members with access modifiers of internal, protected, or private.

  • FlattenHierarchy: Indicates that when searching for static members, public and protected static members inherited from the base class are included, but private static members and nested types are not included.

  • InvokeMethod: Indicates that a method is to be called, which can be a constructor, instance method or static method.

  • CreateInstance: Indicates that to create an instance of a type, call the constructor matching the given parameters.

  • GetField: Indicates that you want to get the value of a field, which can be an instance field or a static field.

  • SetField: Indicates that you want to set the value of a field, which can be an instance field or a static field.

  • GetProperty: Indicates that you want to get the value of a property, which can be an instance property or a static property.

  • SetProperty: Indicates that you want to set the value of a property, which can be an instance property or a static property.

  • PutDispProperty: Indicates that you want to call the PROPPUT member on a COM object to set the value of a property.

  • PutRefDispProperty: Indicates that you want to call the PROPPUTREF member on a COM object, which is used to set the value of a reference type property.

  • ExactBinding: Indicates that the parameter type provided must exactly match the corresponding formal parameter type, and type conversion is not allowed.

  • SuppressChangeType: Indicates that type conversion is prohibited and is only used in COM interop.

  • OptionalParamBinding: Represents a collection of members whose number of returned parameters matches the number of provided parameters, used to handle methods with default values or variable parameters.

  • IgnoreReturn: Indicates that the return value of the method is ignored, used in COM interop.

  • DoNotWrapExceptions: Indicates not to wrap exceptions generated when reflection calls methods in TargetInvocationException.

BindingFlags enumeration members are classified into:

  • Access Modifier: Indicates the visibility of members. There are two options: Public and NonPublic.

  • Member Type: Indicates the type of member, including Constructor, Method, Field, Property, Event and other options.

  • Inheritance Relation: Indicates whether the member comes from the base class or interface. There is an option of DeclaredOnly.

  • Instance or Static: Indicates whether the member is an instance or static. There are two options: Instance and Static.

  • Invoke Method: Indicates whether to ignore case or throw an exception when calling the method. There are multiple options such as IgnoreCase and ThrowOnUnmappableChar.

Please note the following points when using BindingFlags:

  • BindingFlags is a bitfield enumeration, and multiple enumeration values can be combined using the bitwise OR operator (|) to specify multiple binding conditions.

  • When using reflection to find type members, you must specify one of the Instance or Static flags, and one of the Public or NonPublic flags, otherwise an empty array or null value will be returned. For example, if you specify only BindingFlags.Public, no members will be found.

  • When calling a member using reflection, you must specify one of the InvokeMethod, CreateInstance, GetField, SetField, GetProperty, or SetProperty flags to indicate the action to be performed. If multiple operation flags are specified at the same time, an AmbiguousMatchException will be thrown.

  • When using reflection to call members of a COM object, you must use one of the PutDispProperty or PutRefDispProperty flags to distinguish between calls to PROPPUT or PROPPUTREF members. These flags only apply to COM interop scenarios and should not be used for other types of objects.

  • When using reflection to call a method with default values or variadic parameters, the OptionalParamBinding flag must be used and can only be used in conjunction with the InvokeMember method. Also, pay attention to the order and number of parameters to match the correct method signature.

  • When calling a method using reflection, if a custom Binder object is provided, the ExactBinding and SuppressChangeType flags must be ignored because these flags conflict with the semantics of the custom binder. If no custom binder is provided, the default binder uses these flags to decide whether to allow type conversions.

  • When using reflection to call a method, if an exception occurs, reflection will wrap the exception with TargetInvocationException. If you do not want to catch this wrapped exception and instead throw the original exception directly, you can use the DoNotWrapExceptions flag.

The BindingFlags enumeration supports bitwise combinations of its member values, which means we can use the bitwise OR operator (|) to combine multiple BindingFlags to achieve more precise search conditions.

For example:

/ Get public instance methods
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
// Get non-public static fields
BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Static;
// Get the properties declared in the current class
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;

When we use some methods in reflection, we need to pass in a BindingFlags parameter to specify which types of members we want to obtain or operate. For example:?

/ Get all public instance properties in the Person class
Type type = typeof(Person);
PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
//Call the non-public static method named SayHello in the Student class
Type type = typeof(Student);
MethodInfo method = type.GetMethod("SayHello", BindingFlags.NonPublic | BindingFlags.Static);
method.Invoke(null, null);
//Set the value of the public instance field named Name in the Teacher class
Type type = typeof(Teacher);
FieldInfo field = type.GetField("Name", BindingFlags.Public | BindingFlags.Instance);
Teacher teacher = new Teacher();
field.SetValue(teacher, "Tom");

Let’s use an example to help understand. Suppose we have a class as follows: ?

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");
    }
}

This class has six fields and six methods, each using different access modifiers. Now, we want to use reflection to get all the fields and methods of this class and print out their names. We can use the following code to achieve this: ?

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);
}

We used four Flags: BindingFlags.Public, BindingFlags.NonPublic, BindingFlags.Instance and BindingFlags.Static to indicate that we want to obtain all public, non-public, instance and static fields and methods. Running this code, we can get the following output: ?

Fields:
PublicField
PrivateField
ProtectedField
InternalField
ProtectedInternalField
PrivateProtectedField
Methods:
PublicMethod
PrivateMethod
ProtectedMethod
InternalMethod
ProtectedInternalMethod
PrivateProtectedMethod
GetType
MemberwiseClone
Finalize
ToString
Equals
GetHashCode

As you can see, in addition to the six fields and six methods we defined, six other methods are also returned, which are ToString, Equals, GetHashCode, MemberwiseClone, Finalize and GetType. This is because these six methods are inherited from the System.Object class, which is the base class for all .NET types. Since we use BindingFlags.FlattenHierarchy, reflection will return public static members inherited from the base class. If we do not want to return these inherited members, we can use the BindingFlags.DeclaredOnly enumeration value to indicate that only members declared in the TestClass class are returned. The modified code is as follows:?

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);
}

Running this code, we can get the following output: ?

Fields:
PublicField
PrivateField
ProtectedField
InternalField
ProtectedInternalField
PrivateProtectedField
Methods:
PublicMethod
PrivateMethod
ProtectedMethod
InternalMethod
ProtectedInternalMethod
PrivateProtectedMethod

This returns only the fields and methods defined in the TestClass class, excluding members inherited from the base class.

BindingFlags is a very important enumeration type that allows us to flexibly use reflection to obtain and operate members of a class. By understanding the usage and precautions of BindingFlags, we can better utilize the powerful feature of reflection and achieve more functions.

Reference document: ?

https://learn.microsoft.com/en-us/dotnet/api/system.reflection.bindingflags?view=net-7.0
https://www.demo2s.com/csharp/csharp-bindingflags-tutorial-with-examples.html

Import address