Reflection
Reflection is one of the most powerful (and sometimes overlooked) features of C#.
It allows code to inspect and manipulate types, methods, and properties at runtime.
You may not use reflection directly every day, but many tools and frameworks rely on it under the hood.
Understanding reflection will give you better insight into how these tools actually work.
In this post, we’ll cover reflection in the context of:
- Entity Framework (EF)
- JSON serialization
- Dependency Injection
- Unit Testing Frameworks
Entity Framework (EF)
Entity Framework uses reflection to inspect your entity classes, discover their properties, and map them to database columns.
public class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
EF inspects the class at runtime using reflection:
var properties = typeof(User).GetProperties();
foreach (var prop in properties)
{
Console.WriteLine($"{prop.Name} ({prop.PropertyType})");
}
This allows EF to dynamically build SQL queries without you manually defining mappings
JSON Serialization
Libraries like System.Text.Json or Newtonsoft.Json use reflection to read object properties and write them out as JSON.
var user = new User { Id = 1, Name = "Moran" };
var json = JsonSerializer.Serialize(user);
Console.WriteLine(json); // {"Id":1,"Name":"Moran"}
Behind the scenes, the serializer calls GetProperties() on User to figure out what to include in the JSON output.
Dependency Injection
DI containers like Microsoft.Extensions.DependencyInjection use reflection to discover constructors and resolve dependencies automatically.
public class ServiceA { }
public class ServiceB
{
private readonly ServiceA _serviceA;
public ServiceB(ServiceA serviceA) => _serviceA = serviceA;
}
When you request ServiceB, the DI container uses reflection to see that ServiceB has a constructor that requires ServiceA, and then creates the right instance:
services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
Unit Testing Frameworks
Frameworks like NUnit, xUnit, and MSTest use reflection to discover test methods.
public class MyTests
{
[Fact]
public void Should_Add_Correctly()
{
Assert.Equal(4, 2 + 2);
}
}
xUnit uses reflection to find methods decorated with [Fact], then invokes them at runtime without you ever explicitly calling them.
var methods = typeof(MyTests).GetMethods()
.Where(m => m.GetCustomAttributes(typeof(FactAttribute), false).Any());
foreach (var method in methods)
{
method.Invoke(new MyTests(), null);
}
Why Does This Matter?
Reflection makes frameworks flexible and powerful, but it also comes at a cost:
- Performance: Reflection is slower than direct calls.
- Safety: Errors may surface at runtime instead of compile time.
- Complexity: Debugging reflection-heavy code can be tricky. ⸻
Final Thoughts
Reflection is the foundation for many tools you already use — EF, serializers, DI, and test frameworks. By learning how reflection works, you not only deepen your C# fundamentals but also demystify the “magic” behind these tools.


