Developers love reflection because it can save them numerous hours of boilerplate code. But developers also know reflection is slow and it should be used with caution. However, frameworks like ASP.NET Core constantly use the technique to provide their features. So how do they do it?
My name is Ivaylo Kenov, and I am a guest writer at Automate the Planet. I am currently working as a CTO in a large software development team, and I am a huge fan of high-quality software development and clean code. For this reason, I created My Tested ASP.NET – a collection of open-source fluent testing libraries for ASP.NET. In my project, I also use reflection, but I try to optimize it to be as fast as possible.
In this blog post, I would like to share with you one of the cool techniques I learned from the ASP.NET Core code while developing my tools. I optimized my test frameworks more than five times by simply using delegates for my reflection code. I also explain the concepts from this article in this video, so if you prefer to watch me code it live, instead of reading a text, make sure you check it out.
Reflection Definition
Reflection is when managed code can read its own metadata to find assemblies. Essentially, it allows code to inspect other code within the same system. With reflection in C#, you can dynamically create an instance of a type and bind that type to an existing object. Moreover, you can get the type from an existing object and access its properties. When you use attributes in your code, reflection gives you access as it provides objects of Type that describe modules, assemblies, and types.
You need to use Reflection when you want to inspect the contents of an assembly. For example, you can get all members of the object by typing “.” before an object when viewing your Visual Studio editor IntelliSense.
The Plain Old Reflection Code
Let’s first see one of the slowest reflection features – invoking a method. Notably, we are going to invoke a property getter from a class.
public class HomeController
{
public IDictionary<string, object> Data { get; set; }
}
Imagine a situation in which you will need to get the Data dictionary values numerous times via reflection. For example, in the context of a web server processing thousands of requests per minute. This is the straightforward reflection way to retrieve this value.
var homeController = new HomeController();
var homeControllerType = homeController.GetType();
var property = homeControllerType.GetProperty(nameof(HomeController.Data));
var getMethod = property.GetMethod;
var dict = (IDictionary<string, object>)getMethod.Invoke(homeController, Array.Empty<object>());
Now Let’s Optimize it
The idea behind the optimization of the code above is to create a delegate, which will invoke the getter for us. A delegate is way faster than the reflection Invoke method.
var getMethod = property.GetMethod;
var declaringClass = property.DeclaringType;
var typeOfResult = typeof(IDictionary<string, object>);
// Func<ControllerType, TResult>
var getMethodDelegateType = typeof(Func<,>).MakeGenericType(declaringClass, typeOfResult);
// c => c.Data
var getMethodDelegate = getMethod.CreateDelegate(getMethodDelegateType);
var finalDelegate = (Func < HomeController, IDictionary<string, object>)getMethodDelegate;
var dict = finalDelegate(homeController);
Unfortunately, creating the delegate is a slow operation. We need to cache it and reuse it if we want to be super-fast. Additionally, if we are going to use this optimization technique with different classes, we need to make it more abstract. The following PropertyHelper class is a good enough solution.
public class PropertyHelper
{
private static readonly ConcurrentDictionary<Type, PropertyHelper[]> Cache
= new ConcurrentDictionary<Type, PropertyHelper[]>();
private static readonly MethodInfo CallInnerDelegateMethod =
typeof(PropertyHelper).GetMethod(nameof(CallInnerDelegate), BindingFlags.NonPublic | BindingFlags.Static);
public string Name { get; set; }
public Func<object, object> Getter { get; set; }
public static PropertyHelper[] GetProperties(Type type)
=> Cache
.GetOrAdd(type, _ => type
.GetProperties()
.Select(property =>
{
var getMethod = property.GetMethod;
var declaringClass = property.DeclaringType;
var typeOfResult = property.PropertyType;
// Func<Type, TResult>
var getMethodDelegateType = typeof(Func<,>).MakeGenericType(declaringClass, typeOfResult);
// c => c.Data
var getMethodDelegate = getMethod.CreateDelegate(getMethodDelegateType);
// CallInnerDelegate<Type, TResult>
var callInnerGenericMethodWithTypes = CallInnerDelegateMethod
.MakeGenericMethod(declaringClass, typeOfResult);
// Func<object, object>
var result = (Func<object, object>)callInnerGenericMethodWithTypes.Invoke(null, new[] { getMethodDelegate });
return new PropertyHelper
{
Name = property.Name,
Getter = result
};
})
.ToArray());
// Called via reflection.
private static Func<object, object> CallInnerDelegate<TClass, TResult>(
Func<TClass, TResult> deleg)
=> instance => deleg((TClass)instance);
}
The above class will create for us ready to be used delegates and will cache them for each type we provide. Notice how we need to wrap our strongly-typed delegate in a more abstract one to make our cache work.
var controller = new HomeController();
var properties = PropertyHelper.GetProperties(typeof(HomeController));
foreach (var property in properties)
{
Console.WriteLine(property.Name);
Console.WriteLine(property.Getter(controller));
}
Conclusion
By creating and reusing delegates, you can optimize your reflection code multiple times. Some of my measurements showed more than 1000% performance improvement, and that’s a huge one. Try it the next time you are facing reflection, and you will be amazed by the results!
If you liked this article and want to learn more advanced C# techniques, make sure you check out My Tested ASP.NET’s YouTube channel. Happy hacking!
