Reflection · TL;DR

2 min read
Senior14 min read
Rapid overview

TL;DR

Q: What is reflection in C# and when would you use it?

A: Reflection is the ability to inspect and interact with type metadata at runtime via System.Reflection. Use it for scenarios where compile-time type knowledge is insufficient: plugin discovery, attribute-driven frameworks, serialization, DI container auto-registration, and tooling like test runners or ORM materializers.

Q: What is the difference between typeof, GetType(), and Type.GetType()?

A: typeof(T) is a compile-time operator that returns the Type for a known type. obj.GetType() returns the runtime type of an instance (always the concrete type, not the declared type). Type.GetType("name") resolves a type from a string, requiring an assembly-qualified name for types outside the calling assembly or mscorlib.

Q: How do DI containers use reflection internally?

A: They scan assemblies for types implementing specific interfaces, inspect constructors to determine dependencies (ConstructorInfo.GetParameters()), resolve each parameter recursively, and invoke the constructor. Production containers cache constructor delegates after first resolution to avoid repeated reflection.

Q: How do source generators replace reflection?

A: Source generators run during compilation and emit C# source code. Instead of discovering types/properties at runtime, the generated code contains explicit, strongly-typed logic. System.Text.Json source gen, for example, generates serializers that are faster, trimming-safe, and AOT-compatible.

Q: How would you implement a simple plugin system using reflection?

A: Define a shared interface (e.g., IPlugin) in a common assembly. At startup, scan a plugins directory for .dll files, load each via Assembly.LoadFrom, find types implementing IPlugin, and instantiate them with Activator.CreateInstance. For production, add version validation, dependency isolation via AssemblyLoadContext, and error handling for partial loads.

Q: How do you test reflection-heavy code?

A: Write unit tests for discovery logic (ensuring correct types are found) and integration tests that verify attributes/config drive expected behavior. Test trimming scenarios by publishing a trimmed build and running a smoke test. Mock metadata where possible by abstracting the reflection layer behind an interface.

See also