What is reflection in C#, and what are its trade-offs?
Quick Answer
Reflection lets code inspect and invoke types, members and attributes at runtime that weren't known at compile time — typeof/GetType(), GetProperties(), Activator.CreateInstance, reading custom attributes. It powers serializers, DI containers, ORMs and test runners, but it's slow relative to direct calls and fights ahead-of-time compilation and trimming.
Reflection is the ability to examine and manipulate types at runtime — get an object's type, enumerate its properties, read a custom attribute, create an instance from a Type you only know by name. It's what makes DI containers, JSON serializers and ORMs able to work generically without hand-written code for every type. The trade-off is performance — reflected member access is much slower than a direct call unless you cache it or compile it into a delegate — and it's in tension with newer .NET deployment models: Native AOT and trimming want to know statically what code paths exist, so heavy runtime reflection either needs annotations or gets replaced with source generators, which is exactly what System.Text.Json did.
- Caching reflected
MemberInfo/compiled delegates (or usingExpressiontrees /System.Reflection.Emit) avoids repeated lookup cost on hot paths. - Source generators (used by
System.Text.Json, EF Core compiled models) increasingly replace runtime reflection with compile-time generated code — faster and AOT/trimming friendly. - Reflection can break encapsulation (accessing privates) — powerful for tests/tools, dangerous as a general design tool.
- Native AOT publishing needs reflection usage to be explicitly annotated or avoided.