.NET developers are spoiled when it comes to runtime features, type reflection being at the top of the list of “features we didn’t know we needed”. Reflection enables us to obtain information about types defined within our application and all referenced assemblies. While it is rare to use reflection to solve many of our problems, it’s a mechanism that can get us out of tricky situations, dynamically construct objects, and generally improve developer-life quality through extensions and libraries.

We’ll see how we can use reflection dynamically call a public method using a string, access non-public members, and dynamically call a generic method from non-generic code. I find these to be the most common use cases I encounter, but there are many more to explore.

Reflection Basics

When we talk about reflection in .NET, we are explicitly referring to the System.Reflection namespace and our ability to read metadata about types utilizing the System.Type object. The Type object allows us to answer any question around any defined type:

  • What properties exist on this type?
  • What constructors exist on this type?
  • What assembly does this type originate?
  • What methods exist on this type?

Reflection allows us to handle these answers using specific info result objects: ConstructorInfo, MethodInfo, FieldInfo, PropertyInfo, EventInfo, and ParameterInfo. Utilizing the info objects, we can invoke their behavior at runtime, which we’ll see demonstrated later in this post. In layman’s terms, reflection allows us to perform the actions we want, even when we don’t know much about the instances. Like most things in life, there are drawbacks and considerations to think about when reaching for reflection.

Considerations

Reflection is a powerful feature of .NET, but it comes at a performance cost. Reflection is generally slower than traditionally written code. The drawbacks of slow-executing code can be immediately noticeable, especially at production scale. If our code needs to operate at peak efficiency, we should consider other options before reaching for the System.Reflection namespace.

Security is another consideration when dealing with reflection. Member access keywords like public, protected, and private are merely suggestions at compile time. Using reflection, we can disregard an API designer’s intent and do anything we want. Additionally, dynamically executing methods could lead to security vulnerabilities we didn’t expect. In general, it’s a bad idea to accept unbounded user strings that allow for dynamic execution.

Finally, the complexity of reflection isn’t something to be taken lightly. While very powerful, it can take considerable effort to understand what’s happening. When we use reflection, we will almost certainly hit runtime exceptions that we may have caught at compile time.

Reflection Scenarios

We’ve talked a lot about reflection, but let’s look at some everyday use cases for reflection we may find helpful in our attempts to solve complex problems. Remember, in most cases, we should consider other approaches before reflection.

We’ll notice the use of BindingFlags throughout the following samples. The runtime uses BindingFlags as a lookup filter to change what we should be searching to find our target members. The flags will vary based on each use case, so pay attention to which ones we use.

Invoking A Method By String

The first scenario is invoking a method, only knowing its name. The dynamic invocation of methods is typical across many libraries. The approach can invoke a known type’s available method at runtime, but the library author has not taken a direct dependency in their library.

using System;
using System.Reflection;

var instance = new Target();
var method =
    typeof(Target)
        .GetMethod("One",
            BindingFlags.Instance |
            BindingFlags.Public
        );

method.Invoke(instance, parameters:null);

public class Target
{
    public void One() => Console.WriteLine("One");
}

Invoking Private Methods

We can also invoke private methods and retrieve their value. Notice how this code is very similar to our first example, except now we are passing a BindingFlags.NonPublic value into our GetMethod call.

using System;
using System.Reflection;

var instance = new Target();
var method =
    typeof(Target)
        .GetMethod("GetSecret",
            BindingFlags.Instance |
            BindingFlags.NonPublic
        );

var result =
    method.Invoke(instance, parameters:null);

Console.WriteLine(result);

public class Target
{
    private string GetSecret() => "42";
}

Accessing A Private Field

Sometimes the data we need is private or protected, it’s not a great idea to access these members, but sometimes we have to. Let’s look at accessing a private string field.

using System;
using System.Reflection;

var instance = new Target();
var field =
    typeof(Target)
        .GetField("secret",
            BindingFlags.Instance |
            BindingFlags.NonPublic
        );

var value =
    (string)field.GetValue(instance);

Console.WriteLine(value);

public class Target
{
    private string secret = "42";
}

Invoking Generic Methods

Generics are a powerful feature of C# and .NET in general. Developers can create reusable constructs. Consuming generically-defined code at development time is straightforward. Invoking the same generic types and methods at runtime can be a bit more complicated.

using System;
using System.Reflection;

var instance = new Target();
var method =
    typeof(Target)
        .GetMethod("Generic",
            BindingFlags.Instance |
            BindingFlags.Public 
        );

var generic = 
    method.MakeGenericMethod(typeof(string));

var result =
    generic.Invoke(instance, new[] {"Khalid"});

Console.WriteLine(result);

public class Target
{
    public string Generic<T>(T input) 
        => $"Hello {input}!";
}

Retrieving All Public Members

Sometimes we want to list all the public members of a type that we declared on the class itself.

using System;
using System.Linq;
using System.Reflection;

var instance = new Target();
var members =
    typeof(Target)
        .GetMembers(
            BindingFlags.Instance |
            BindingFlags.Public |
            BindingFlags.DeclaredOnly
        )
        .Where(x =>
            !x.Name.StartsWith("get_") &&
            !x.Name.StartsWith("set_")
        )
        .ToList();

foreach(var member in members)
    Console.WriteLine(member.Name);

public class Target
{
    public void Zero() {}
    public string One { get; set; }
    public string Two { get; set; }
}

The resulting output of this code is a bit, tricky. We see the constructor in the list. We also have to exclude get and set methods generated for each property. While seeing these methods is technically correct, we most likely want to access properties via the PropertyInfo type.

Zero
.ctor
One
Two

Using ReflectionMagic

If we want to live on the dynamic side of life, there is a NuGet package called ReflectionMagic, which allows us to cast our instance object to a dynamic and invoke any member.

> dotnet add package ReflectionMagic

When using this package, we need to be aware that misspellings will not be caught at compile time. It’s dynamic so there are no compiler assurances. We’re living on the edge!

using System;
using ReflectionMagic;

var instance = new Target();
Console.WriteLine(instance.AsDynamic().secret);

public class Target
{
    private string secret = "42";
}

The trade-off between using this NuGet package is a much more simplified interaction with our object instances but less control over the invocation and access to our members. Folks who need more constraints around reflection code should likely roll their own methods.

Conclusion

There you have it, some everyday use cases for reflection that you’ll likely run into during your .NET development career. We should always consider non-reflection approaches first, but it is still nice to know that this functionality exists when we need it. Understanding what BindingFlags are required may take some trial and error when writing this code. The GetMembers method can return high-level metadata about our type, but we likely want to use the more specialized info types to manipulate our instances. Finally, we can use a library like ReflectionMagic to simplify access to instance members, even if it does come with a slightly elevated risk of making human mistakes like misspelling a field name.

I hope you found this post enlightening, and if you’re a reflection guru, please let me know in the comments what kinds of reflection you use day-to-day.

References