Reflection is the ability to inspect and manipulate types, methods, properties, and other members at runtime — without knowing them at compile time. It is the foundation of many .NET framework features: ASP.NET Core’s model binding discovers controller action parameters via reflection, the DI container discovers constructors via reflection, EF Core discovers entity properties via reflection, and JSON serialisers use reflection to find which properties to serialise. Understanding reflection helps you understand how these frameworks work and lets you build your own dynamic, metadata-driven features.
Getting Type Information
// ── Three ways to get a Type object ───────────────────────────────────────
Type type1 = typeof(Post); // compile-time known type — preferred
Type type2 = post.GetType(); // instance's runtime type
Type type3 = Type.GetType("BlogApp.Domain.Entities.Post, BlogApp.Domain"); // by name
// ── Inspecting type information ────────────────────────────────────────────
Console.WriteLine(type1.Name); // "Post"
Console.WriteLine(type1.FullName); // "BlogApp.Domain.Entities.Post"
Console.WriteLine(type1.Namespace); // "BlogApp.Domain.Entities"
Console.WriteLine(type1.IsClass); // true
Console.WriteLine(type1.IsInterface); // false
Console.WriteLine(type1.IsAbstract); // false
Console.WriteLine(type1.IsGenericType); // false
// ── Listing members ────────────────────────────────────────────────────────
// Properties
foreach (var prop in type1.GetProperties())
Console.WriteLine($" {prop.PropertyType.Name} {prop.Name} " +
$"[get:{prop.CanRead}, set:{prop.CanWrite}]");
// Methods
foreach (var method in type1.GetMethods(BindingFlags.Public | BindingFlags.Instance))
Console.WriteLine($" {method.ReturnType.Name} {method.Name}()");
// Fields
foreach (var field in type1.GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
Console.WriteLine($" {field.FieldType.Name} {field.Name}");
GetProperty(), GetValue(), and Invoke() are orders of magnitude slower than direct property access and method calls. They also bypass compile-time type checking. In application code, always prefer direct member access; use reflection only when you genuinely need to work with types unknown at compile time (plugin systems, serialisers, attribute-driven validation). If you need repeated reflective access in a hot path, cache the PropertyInfo/MethodInfo objects or compile them to delegates.methodInfo.Invoke(target, args) on every request, create a delegate once: var del = (Action<Post>)Delegate.CreateDelegate(typeof(Action<Post>), methodInfo). The delegate call is nearly as fast as a direct call, while the compilation happens only once. This technique is used by ASP.NET Core’s model binder and EF Core internally for performance-critical reflective operations.GetField("_privateField", BindingFlags.NonPublic | BindingFlags.Instance) gives you access to private fields. Use this power responsibly — bypassing encapsulation makes code fragile and breaks when the target type’s implementation changes. Private members are private for a reason. In tests, prefer testing public behaviour over poking at private implementation details through reflection.Dynamic Object Creation and Method Invocation
// ── Create an instance dynamically ────────────────────────────────────────
Type postType = typeof(Post);
// Using Activator — simplest for parameterless constructors
object? instance = Activator.CreateInstance(postType);
// Using constructor info — for parameterised constructors
ConstructorInfo? ctor = postType.GetConstructor(new[] { typeof(string), typeof(string) });
object? post = ctor?.Invoke(new object[] { "Hello", "author-1" });
// ── Read and write properties dynamically ─────────────────────────────────
PropertyInfo? titleProp = postType.GetProperty("Title");
PropertyInfo? viewsProp = postType.GetProperty("ViewCount");
if (post is not null)
{
titleProp?.SetValue(post, "New Title via Reflection");
object? title = titleProp?.GetValue(post);
Console.WriteLine(title); // "New Title via Reflection"
}
// ── Invoke methods dynamically ─────────────────────────────────────────────
MethodInfo? publishMethod = postType.GetMethod("Publish");
publishMethod?.Invoke(post, null); // no parameters
// ── Generic method invocation ──────────────────────────────────────────────
MethodInfo? genericMethod = typeof(Repository)
.GetMethod("GetByIdAsync")!
.MakeGenericMethod(typeof(Post)); // Repository.GetByIdAsync<Post>(id)
Common Mistakes
Mistake 1 — Using reflection in a hot path without caching (performance)
❌ Wrong — GetProperty and GetValue called on every request:
foreach (var post in posts)
{
var prop = typeof(Post).GetProperty("Title"); // expensive — called per item!
title = (string?)prop?.GetValue(post);
}
✅ Correct — cache the PropertyInfo outside the loop:
var titleProp = typeof(Post).GetProperty("Title"); // cached once
foreach (var post in posts)
title = (string?)titleProp?.GetValue(post); // reuse PropertyInfo
Mistake 2 — Using reflection where source generators or interfaces suffice
If you need to call a method on a set of types, define an interface and use polymorphism. Reserve reflection for cases where type names come from external sources at runtime (plugins, configuration, serialisation).