.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.