Course contentsShow
← C#
Lesson 1768 of 1,90816. Performance and DiagnosticsFree lesson

Profiling Reflection-Based Workloads

Learners will measure the overhead of dynamic code execution and implement caching for reflection metadata.

In this lesson, you will learn how to quantify the hidden performance costs of reflection and mitigate them by caching metadata access.

Reflection is a powerful tool for building flexible, decoupled architectures, but it is notoriously expensive. Every time you call Type.GetProperty or MethodInfo.Invoke, the runtime must perform intensive metadata lookups, security checks, and boxing operations. In high-performance systems, these "hidden" costs quickly accumulate, showing up as hot paths in your flame graphs or high CPU usage in DotTrace timelines.

Measuring the Overhead

When profiling reflection-heavy code, the first step is to establish a performance baseline using BenchmarkDotNet. Reflection overhead is often underestimated because it appears as System.Reflection calls deep within the stack. By measuring these calls specifically, you can isolate the latency added by the metadata lookup versus the actual logic execution.

Implementing Metadata Caching

To minimize this overhead, we avoid repeated lookups by caching the result of the reflection operation. Instead of querying for a PropertyInfo every time you need to set a value, you retrieve it once and store it in a static dictionary or a concurrent collection.

private static readonly ConcurrentDictionary<string, PropertyInfo> _propertyCache = new();

public void SetValueOptimized(object obj, string propertyName, object value)
{
    // Retrieve metadata once, then reuse
    var property = _propertyCache.GetOrAdd(propertyName, name => 
        obj.GetType().GetProperty(name));
        
    property?.SetValue(obj, value);
}

By caching the PropertyInfo (or even better, a compiled Delegate or Expression), you bypass the expensive search process for every subsequent call.

Going Further with Compiled Expressions

If your workload is truly extreme, caching the PropertyInfo is only the first step. You can use Expression.Lambda to compile a strongly-typed delegate that performs the set/get operation. This effectively converts the reflection-based access into a standard method call, which the JIT compiler can then inline, effectively reducing the overhead to near-zero.

Key Takeaway: Reflection overhead stems from repeated metadata lookups; always cache resolved metadata or compile delegates to ensure your dynamic workflows remain performant.