The Quiet Death of Runtime Reflection in .NET
For twenty years, reflection powered DI containers, ORMs, and serializers. Source generators and AOT are replacing it — not because…
Software Development Principles
The Quiet Death of Runtime Reflection in .NET
For twenty years, reflection powered DI containers, ORMs, and serializers. Source generators and AOT are replacing it — not because reflection is slow, but because the runtime is disappearing.

Photo by Nemuel Sereti on Pexels
In 2002, when .NET Framework 1.0 shipped, the System.Reflection namespace was a revelation. For the first time in the Microsoft ecosystem, code could examine itself. You could load an assembly at runtime, discover its types, invoke methods by name, construct objects without knowing their concrete class. The JIT compiler was always there to warm things up, and the garbage collector would handle the rest.
This was extraordinary. Java had reflection too, but .NET’s implementation was deeply integrated with the Common Type System — metadata wasn’t an afterthought, it was foundational. An entire generation of frameworks grew from this capability: dependency injection containers that resolve types by scanning assemblies, ORMs that map properties to columns by reading attributes, serializers that convert objects to JSON by walking the type graph at runtime.
For twenty years, this worked. The runtime was always present, the JIT was always available, and the cost of reflection — the allocation, the type inspection, the late binding — was acceptable because the machine running the code was known, powerful, and patient.
Three things changed. And together, they’re ending an era.
The punctuation mark
In evolutionary biology, Stephen Jay Gould and Niles Eldredge proposed a model called punctuated equilibrium: species don’t evolve gradually. They stay stable for long periods, then change rapidly in short bursts triggered by environmental pressure. The fossil record shows it — millions of years of morphological stability, then sudden diversification.
.NET’s relationship with reflection has followed the same pattern. Two decades of stability. Then, between 2020 and 2025, three environmental pressures arrived nearly simultaneously.

Timeline: .NET’s shift from runtime reflection to compile-time code generation
Pressure 1: Native AOT compilation. Starting with .NET 7 and maturing in .NET 8–9, Ahead-of-Time compilation strips the JIT from the deployment entirely. No runtime code generation. No dynamic assembly loading. The binary is the binary. Reflection in AOT mode is severely limited — Type.GetType() by string fails, Activator.CreateInstance() may fail, and any type not statically reachable gets trimmed. The runtime that reflection depended on is literally being removed.
Pressure 2: Source generators. Introduced in .NET 5 and dramatically improved since, source generators let you analyze code at compile time and emit additional C# files before compilation completes. The work reflection did at runtime — scanning types, reading attributes, generating mappings — can now happen once, at build time, producing concrete code that the compiler can optimize like any other code.
Pressure 3: Cloud economics. Cold start time matters in serverless. Memory footprint matters in containers. Startup time matters everywhere. Reflection is inherently a warm-up cost — it does work on first access that compile-time approaches avoid entirely. When you’re paying per millisecond and per megabyte, the economics shift.
Twenty years of stability. Then three pressures at once. Punctuated equilibrium in action — the ecosystem isn’t evolving gradually. It’s jumping.
What’s already moved
The shift isn’t theoretical. Major .NET libraries have already migrated or are actively migrating:
System.Text.Json — Microsoft’s own JSON serializer shipped with source generator support in .NET 6. When you use [JsonSerializable(typeof(MyType))], the serializer generates concrete read/write code at compile time. No reflection, no runtime type inspection, no first-call warm-up cost. The result: faster serialization, smaller trimmed binaries, and full AOT compatibility.
// Before: runtime reflection (still works, but slower + breaks AOT)
var json = JsonSerializer.Serialize(order);
// After: compile-time source generation
[JsonSerializable(typeof(Order))]
partial class AppJsonContext : JsonSerializerContext { }
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
The API is slightly more verbose. The performance gain is 2–3x for serialization, the binary size drops measurably when trimming is enabled, and — crucially — it works under Native AOT where the reflection-based path throws at runtime.
Microsoft.Extensions.DependencyInjection — the default DI container is getting compile-time registration analysis. Today, services.AddTransient<IService, Implementation>() resolves at runtime. The direction is clear: generate the resolution graph at compile time, with fallback to runtime only for genuinely dynamic scenarios.
Entity Framework Core — compiled models, introduced in EF Core 6, pre-generate the metadata model at build time instead of building it from reflection on first DbContext creation. This cuts startup time significantly for large models. The trajectory is toward more compile-time analysis, less runtime discovery.
Minimal APIs — .NET’s minimal API framework already uses source generators for request delegate generation. The [AsParameters] attribute triggers compile-time binding code instead of runtime model binding via reflection.

Comparison: libraries that have moved from runtime reflection to compile-time code generation
This isn’t just a performance optimization. It’s an architectural change in how .NET frameworks will be designed.
Runtime reflection creates a specific dependency: your code depends on metadata that exists in the binary but is only consulted at runtime. This means errors are late — you discover missing types, wrong attributes, or misconfigured mappings when the code runs, not when it compiles. Every .NET developer has seen the MissingMethodException or the silent DI resolution failure that only appears in production.
Compile-time code generation inverts this. The source generator reads your code, generates the mapping or registration code, and the C# compiler checks it. Type mismatches become compile errors. Missing registrations become build failures. The feedback loop tightens from “deploy and discover” to “save and discover.”
This is the deeper shift. Not speed — correctness feedback at compile time instead of runtime.
The trade-off is flexibility. Reflection-based frameworks can handle types they’ve never seen before — plugin architectures, dynamic assembly loading, runtime composition. Source generators can only process what’s visible at build time. Systems that genuinely need runtime discovery (plugin hosts, scripting engines, some testing frameworks) will keep using reflection. But most line-of-business applications don’t load assemblies at runtime. They register known types at startup and resolve them predictably.
For those applications — which is most applications — compile-time generation is strictly better.
The skeleton of a source generator that replaces a common reflection pattern is surprisingly compact:
[Generator]
public class MapperGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext ctx)
{
var classes = ctx.SyntaxProvider
.ForAttributeWithMetadataName(
"MyApp.AutoMapAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => (INamedTypeSymbol)ctx.TargetSymbol);
ctx.RegisterSourceOutput(classes, (spc, symbol) =>
{
var code = GenerateMapper(symbol); // emit property-by-property mapping
spc.AddSource($"{symbol.Name}_Mapper.g.cs", code);
});
}
}
What took fifty lines of reflection — GetProperties(), SetValue(), boxing, Activator.CreateInstance() — becomes a generated file with direct property assignments. The compiler sees it, optimizes it, and reports errors at build time. No runtime surprises.
What would falsify this prediction
This prediction could be wrong if:
AOT adoption stalls. If the .NET ecosystem decides that AOT’s limitations (no runtime codegen, no dynamic loading, limited reflection) are too restrictive for mainstream use, the pressure to abandon reflection weakens. Watch the adoption curve: if fewer than 30% of new .NET projects target AOT by 2028, the migration will be slower than I expect.
Source generators hit a complexity ceiling. Today’s source generators work well for serialization, DI, and mapping — relatively mechanical code generation. If more complex scenarios (like full ORM query translation or dynamic middleware pipelines) prove too difficult to express as source generators, reflection retains its role. Watch for library maintainers reverting from source generators back to reflection — that’s the signal.
A new runtime architecture emerges. If .NET introduces a lightweight reflection alternative that’s AOT-compatible and low-overhead — essentially metadata without the cost — the migration path changes. Not impossible, but it would require a fundamental change to how the CLR handles type information.
The ecosystem resists the verbosity tax. Source generators require more ceremony than reflection. JsonSerializer.Serialize(obj) becomes JsonSerializer.Serialize(obj, AppJsonContext.Default.Order) — an extra parameter, an extra class, an attribute, a partial keyword. For every library that migrates, developers pay this tax at every call site. If the community decides the ergonomic cost outweighs the performance and correctness gains, adoption slows. The tooling can help — IDE refactoring, Roslyn analyzers that auto-suggest the generated overload — but the resistance is real and worth watching.
What to watch for
The signal that this shift is acceleratin
g:The transition won’t be dramatic. Reflection isn’t being removed from .NET — it’s being demoted. From the primary mechanism to a fallback. From the default choice to the escape hatch.
I’ve been writing .NET code since the Framework 2.0 days, and reflection always felt like magic — the runtime knows things about your code that your code doesn’t know about itself. There’s a loss in moving away from that. Reflection made frameworks feel alive — they adapted to your code without you having to explain yourself. The new world is more explicit, more verbose, more visible. You can read the generated code. You can debug it. You can understand exactly what happens at startup.
That’s not magic. It’s careful, deliberate engineering. And slowly, .NET is choosing engineering over magic. I think that’s the right trade, even if I’ll miss the elegance of Assembly.GetTypes() doing something impossible at 3am on a Tuesday.
- NuGet package compatibility labels. When major packages start advertising “AOT-compatible” or “reflection-free” as a selling point, the economic pressure has reached library authors.
**RequiresUnreferencedCodewarnings in your build.** These trimmer warnings are the canary. Every warning is a line of code that won't survive the AOT transition.- New frameworks choosing source generators first. When the default choice for a new .NET library is “write a source generator” instead of “use reflection,” the cultural shift is complete.
메타데이터
- post_id
- df9afc5c77dd
- slug
- the-quiet-death-of-runtime-reflection-in-net-df9afc5c77dd
- url
- https://medium.com/@antonellosemeraro/the-quiet-death-of-runtime-reflection-in-net-df9afc5c77dd
- canonical_url
- https://medium.com/@antonellosemeraro/the-quiet-death-of-runtime-reflection-in-net-df9afc5c77dd
- author_url
- https://medium.com/@antonellosemeraro
- status
- ok
- fetched_at
- 2026-06-10 08:34:46