C#
reflection
programming
software development
performance optimization

Efficient use of reflection in C

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Reflection in C# is useful when code must discover types, methods, or attributes at runtime. It is also slower and less type-safe than direct calls, so the goal is not to avoid it entirely but to keep it off hot paths and use it deliberately. Efficient reflection usually means doing the expensive discovery once, caching the result, and then switching to compiled delegates or normal code paths whenever possible.

What Reflection Is Good For

Reflection is appropriate when the program genuinely does not know the types or members at compile time. Common examples include:

  • plugin discovery
  • attribute-based registration
  • serializers and mappers
  • testing or tooling infrastructure
  • dependency injection containers

If the type is already known at compile time, reflection is usually the wrong tool. In that case, direct access is faster, safer, and easier to refactor.

The Cost Comes From Repeated Metadata Lookups

The biggest reflection mistake is doing repeated metadata discovery inside a tight loop.

csharp
1using System;
2using System.Reflection;
3
4public class User
5{
6    public string Name { get; set; } = "";
7}
8
9PropertyInfo property = typeof(User).GetProperty("Name")!;
10var user = new User { Name = "Ana" };
11Console.WriteLine(property.GetValue(user));

This is fine once. It becomes expensive if GetProperty happens thousands of times during request processing or serialization.

Cache Metadata

If you need reflection repeatedly, cache the Type, PropertyInfo, MethodInfo, or attribute results.

csharp
1using System;
2using System.Collections.Concurrent;
3using System.Reflection;
4
5public static class PropertyCache
6{
7    private static readonly ConcurrentDictionary<(Type, string), PropertyInfo?> Cache = new();
8
9    public static PropertyInfo? GetProperty(Type type, string name)
10    {
11        return Cache.GetOrAdd((type, name), key => key.Item1.GetProperty(key.Item2));
12    }
13}

This turns repeated discovery into one lookup plus cheap cache hits. Many frameworks do exactly this internally.

Compile Delegates For Repeated Access

If you are repeatedly reading or writing a property, cached metadata alone may still not be ideal. A common next step is to compile a delegate once and call that delegate instead of GetValue or SetValue each time.

csharp
1using System;
2using System.Linq.Expressions;
3
4public static Func<T, object?> BuildGetter<T>(string propertyName)
5{
6    var parameter = Expression.Parameter(typeof(T), "x");
7    var property = Expression.Property(parameter, propertyName);
8    var convert = Expression.Convert(property, typeof(object));
9    return Expression.Lambda<Func<T, object?>>(convert, parameter).Compile();
10}
11
12var getter = BuildGetter<User>("Name");
13var user = new User { Name = "Ana" };
14Console.WriteLine(getter(user));

This keeps reflection in the setup phase but removes it from the inner execution path.

Prefer Attributes And Conventions Carefully

Reflection is often combined with attributes. That is powerful, but attribute scanning should also be done once at startup or registration time rather than on every request.

csharp
1using System;
2
3[AttributeUsage(AttributeTargets.Class)]
4public class HandlerAttribute : Attribute
5{
6}
7
8[Handler]
9public class OrderHandler
10{
11}

You can scan loaded assemblies once, register the matches, and then use normal strongly typed execution afterward.

Avoid Reflection In Tight Business Loops

If you are writing application code and reflection ends up inside every row mapping, every JSON property access, or every request-scoped authorization check, you should stop and redesign. Reflection is best used to build a map, registry, or delegate layer that the rest of the program uses efficiently.

A good rule is:

  • reflection during startup or registration: often fine
  • reflection inside critical request or compute loops: usually a smell

Security And Maintainability

Reflection can bypass normal compile-time guarantees, which means mistakes show up later and can be harder to diagnose. Be especially careful when type or member names come from external input. Resolve those names against a whitelist or known registry rather than exposing raw reflection over arbitrary user-provided strings.

Common Pitfalls

  • Calling GetType, GetProperty, or Invoke repeatedly inside hot loops.
  • Using reflection for code that could have been expressed with interfaces, generics, or normal polymorphism.
  • Caching too little, such as caching Type but not the expensive member lookup results.
  • Treating reflection-based code as harmless even when member names come from untrusted input.
  • Forgetting that compiled delegates often give most of the flexibility with much less runtime overhead.

Summary

  • Reflection is useful for dynamic discovery, registration, and metadata-driven behavior.
  • The expensive part is repeated runtime lookup and invocation, not the concept itself.
  • Cache metadata and compile delegates when reflection-backed access happens often.
  • Keep reflection in setup paths when possible and out of hot execution loops.
  • If the type is known at compile time, prefer normal typed code over reflection.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.