1. Introduction
Reflection is the ability of your program to inspect its own structure at runtime. Imagine your program asking itself: 'What methods do I have? What properties does this type have?' and getting an answer at runtime.
Real-world use cases for reflection
- Building plugins and modules: You don't need to know the class ahead of time — just load a DLL and discover its methods on the fly.
- Automated testing: Test frameworks like xUnit and NUnit find test methods via reflection.
- Serialization: With reflection you can automatically turn any object into JSON/XML without manually iterating properties.
- Validating data by attributes: For example, you added a [Required] attribute to a property — and you want to automatically validate all such properties.
Why this matters in interviews
Reflection skills are often asked about in interviews because they show your understanding of how .NET works and your ability to solve dynamic problems. If you want to write your own frameworks or extensions, reflection is indispensable.
2. Namespace and basic reflection classes
All reflection functionality in C# is available via the System.Reflection namespace. Don't forget at the top of your file:
using System.Reflection;
Main reflection types
| Class/Type | Description |
|---|---|
|
Represents a type description in .NET (for example, class, interface, enum, etc.) |
|
A type's property |
|
A type's method |
|
A type's field |
|
A type's constructor |
|
Represents an assembly (EXE or DLL) |
Key methods on Type
| Method | Description |
|---|---|
|
Returns all public properties |
|
Returns all public fields |
|
Returns all public methods |
|
Returns all public constructors |
|
Returns all interfaces implemented by the type |
|
Returns all attributes applied to the type |
You can often pass BindingFlags to these methods to access private or static members, but for now we'll stick to the basic scenario.
3. The very first step: getting a Type object
All reflection techniques are built around working with a Type object.
There are several ways to get that object, let's look at each:
Way 1: From an object
string text = "Hello, Reflection!";
Type type1 = text.GetType();
Console.WriteLine(type1.Name); // String
Way 2: From the type name
Type type2 = typeof(int);
Console.WriteLine(type2.FullName); // System.Int32
Way 3: By string (dynamically)
Type type3 = Type.GetType("System.Double");
Console.WriteLine(type3); // System.Double
Be careful! For custom classes from other assemblies you need to provide the full type name including the assembly.
4. Exploring a type's structure: properties, methods, fields and constructors
Here comes the moment of truth: we have a Type object — now we can learn anything we want.
Getting the list of properties
Type type = typeof(DateTime);
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine($"{prop.PropertyType.Name} {prop.Name}");
}
Output:
Int32 Day
Int32 Month
Int32 Year
DayOfWeek DayOfWeek
...
Getting the list of methods
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
Console.WriteLine($"{method.ReturnType.Name} {method.Name}()");
}
Big types can have lots of methods! Often you need not all of them but only "your own" ones (for example, excluding inherited methods from object). We'll cover that later.
Getting the list of fields
FieldInfo[] fields = type.GetFields();
foreach (var field in fields)
{
Console.WriteLine($"{field.FieldType.Name} {field.Name}");
}
But most normal classes don't have public fields, because encapsulation is everything :)
Getting constructors
ConstructorInfo[] constructors = type.GetConstructors();
foreach (var ctor in constructors)
{
Console.WriteLine($"Constructor: {ctor}");
}
5. Applying reflection in practice: inspecting our app
Recall that in our learning project there's a class TaskItem where we store tasks. Let's inspect it via reflection.
public class TaskItem
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DueDate { get; set; }
public bool IsCompleted;
}
Here's how to see the whole "insides" of our type at runtime:
Type taskType = typeof(TaskItem);
Console.WriteLine("Properties:");
foreach (var prop in taskType.GetProperties())
Console.WriteLine($" {prop.PropertyType.Name} {prop.Name}");
Console.WriteLine("Fields:");
foreach (var field in taskType.GetFields())
Console.WriteLine($" {field.FieldType.Name} {field.Name}");
Console.WriteLine("Methods:");
foreach (var method in taskType.GetMethods())
Console.WriteLine($" {method.ReturnType.Name} {method.Name}()");
Try adding new properties to the class and see how reflection picks them up. That's how general serializers and object inspectors work.
6. Visualization: what a Type object looks like (diagram)
+-------------------------+
| Type |
+-------------------------+
| .Name |
| .FullName |
| .Namespace |
| .Assembly |
+-------------------------+
| .GetProperties() |
| .GetFields() |
| .GetMethods() |
| .GetConstructors() |
| .GetInterfaces() |
| .GetCustomAttributes() |
+-------------------------+
This is our "type portrait" — it has a name, assembly, a set of members and attributes.
Getting basic info about a type
A Type object can tell you what kind of type it is:
Type t = typeof(double);
Console.WriteLine(t.Name); // Double
Console.WriteLine(t.FullName); // System.Double
Console.WriteLine(t.Namespace); // System
Console.WriteLine(t.IsClass); // False
Console.WriteLine(t.IsValueType); // True
Console.WriteLine(t.IsEnum); // False
Console.WriteLine(t.IsPrimitive); // True
7. Finding a specific property, method or field by name
Property
var prop = taskType.GetProperty("Title");
if (prop != null)
{
Console.WriteLine($"Property 'Title' type: {prop.PropertyType}");
}
Method
var method = taskType.GetMethod("ToString");
if (method != null)
{
Console.WriteLine($"Method 'ToString' returns: {method.ReturnType}");
}
Field
var field = taskType.GetField("IsCompleted");
if (field != null)
{
Console.WriteLine($"Field 'IsCompleted' type: {field.FieldType}");
}
8. Using reflection for dynamic value access
Now we don't just look — we also touch values!
Reading a property value
TaskItem item = new TaskItem { Id = 1, Title = "Test reflection", DueDate = DateTime.Today };
PropertyInfo titleProp = item.GetType().GetProperty("Title");
if (titleProp != null)
{
object value = titleProp.GetValue(item);
Console.WriteLine($"Title: {value}");
}
Setting a property value
titleProp.SetValue(item, "Modified title");
Console.WriteLine(item.Title);
Working with fields
FieldInfo completedField = item.GetType().GetField("IsCompleted");
completedField.SetValue(item, true);
Console.WriteLine(item.IsCompleted); // true
For private fields and properties you need special flags, for example BindingFlags.NonPublic. We'll cover that later.
9. Invoking methods via reflection
You can invoke methods even if you didn't know them at compile time!
TaskItem task = new TaskItem { Title = "Reflection is cool!" };
MethodInfo method = task.GetType().GetMethod("ToString");
if (method != null)
{
object result = method.Invoke(task, null);
Console.WriteLine(result);
}
If a method has parameters, pass them as an object array:
public class Calculator
{
public int Add(int a, int b) => a + b;
}
var calc = new Calculator();
var addMethod = typeof(Calculator).GetMethod("Add");
object[] parameters = { 5, 3 };
object sumResult = addMethod.Invoke(calc, parameters);
Console.WriteLine(sumResult); // 8
10. Dynamically creating objects via constructor
Reflection lets you create objects without the new operator!
Type taskType = typeof(TaskItem);
object newTask = Activator.CreateInstance(taskType);
// It's the same TaskItem (but typed as object)
Console.WriteLine(newTask.GetType().Name); // TaskItem
You can pass constructor parameters:
public class User
{
public string Name { get; }
public User(string name) { Name = name; }
}
Type userType = typeof(User);
object user = Activator.CreateInstance(userType, "Alice");
Console.WriteLine(((User)user).Name); // Alice
11. Common mistakes when working with System.Reflection
Mistake #1: Accessing private members without BindingFlags.
By default reflection sees only public members. Without BindingFlags.NonPublic you can't access private fields or methods.
Mistake #2: Ignoring null checks.
Methods like GetProperty, GetMethod and others can return null if the member isn't found. Without checking this you'll get a NullReferenceException.
Mistake #3: Picking the wrong method overload.
When there are multiple overloads, GetMethod might return the wrong one. Use GetMethod with parameter types specified.
Mistake #4: Using reflection in performance-critical code.
Reflection is slower than direct calls. For high-performance code use metadata caching or delegates.
GO TO FULL VERSION