Introduction
Reflection is a way to inspect and interact with types (classes, interfaces, methods, properties, etc.) at runtime. It lets your code look at other code, understand it, and even modify or invoke it without knowing about it at compile-time.
If you ever wanted to:
-
Get a list of properties of a class,
-
Call a method by name as a string,
-
Create an object of a class without using new
,
-
Access private members of a class,
Then Reflection is what you need.
Why do we need Reflection?
Most of the time, you know which class you’re working with. But sometimes, your code needs to be dynamic. For example:
-
You are building a plugin system and loading classes from DLLs.
-
You are reading an assembly at runtime and want to analyze its types.
-
You are writing a generic serializer or logger that should work with any class.
-
You need to invoke a method by name given as user input or configuration.
Reflection is powerful, but it should be used carefully because:
-
It is slower than normal code.
-
It can break encapsulation (e.g., access private fields).
-
It can lead to runtime errors if something doesn’t exist.
Where is Reflection defined?
Reflection lives in the System.Reflection
namespace.
using System.Reflection;
Core Reflection Classes
Here are the most important classes you'll use:
Class |
Purpose |
Type |
Represents type information (class, struct, interface, etc.) |
MethodInfo |
Represents a method |
PropertyInfo |
Represents a property |
FieldInfo |
Represents a field |
ConstructorInfo |
Represents a constructor |
Assembly |
Represents a .NET assembly (DLL or EXE) |
Getting Type Information using Type
You can get the type in three ways:
// 1. From an object
var type1 = myObject.GetType();
// 2. Using typeof
var type2 = typeof(MyClass);
// 3. From a string (late binding)
var type3 = Type.GetType("Namespace.MyClass");
Example. Get Properties of a Class
Type type = typeof(Person);
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine($"Property: {prop.Name}, Type: {prop.PropertyType}");
}
Example. Get Methods of a Class
MethodInfo[] methods = typeof(Person).GetMethods();
foreach (var method in methods)
{
Console.WriteLine($"Method: {method.Name}");
}
Creating an Object at Runtime
You can create an object without using new
by using Activator
.
Type type = typeof(Person);
object obj = Activator.CreateInstance(type);
If the constructor needs parameters:
object obj2 = Activator.CreateInstance(type, "John", 25);
Invoking a Method by Name
Let’s say you want to call the SayHello
method dynamically:
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(obj, null);
If the method has parameters:
method.Invoke(obj, new object[] { "Hello" });
Accessing Fields and Properties
Getting and setting property value:
PropertyInfo prop = type.GetProperty("Name");
// Get value
var value = prop.GetValue(obj);
// Set value
prop.SetValue(obj, "Ravi");
Getting and setting field value:
FieldInfo field = type.GetField("age", BindingFlags.NonPublic | BindingFlags.Instance);
// Get value
var age = field.GetValue(obj);
// Set value
field.SetValue(obj, 30);
Note: For private fields, you must use BindingFlags.NonPublic
and BindingFlags.Instance
.
Working with Constructors
ConstructorInfo[] constructors = type.GetConstructors();
foreach (var ctor in constructors)
{
Console.WriteLine($"Constructor: {ctor}");
}
You can also invoke constructors directly:
ConstructorInfo ctor = type.GetConstructor(new[] { typeof(string), typeof(int) });
object person = ctor.Invoke(new object[] { "John", 25 });
Loading an Assembly Dynamically
Assembly assembly = Assembly.LoadFrom("MyLibrary.dll");
Type[] types = assembly.GetTypes();
foreach (var t in types)
{
Console.WriteLine($"Type: {t.FullName}");
}
Binding Flags – Controlling What You See
By default, you only get public instance members. If you want to access private, static, etc., use BindingFlags
.
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.Static;
FieldInfo[] fields = type.GetFields(flags);
Real-world Example: Simple Logger using Reflection
public static void LogObjectProperties(object obj)
{
Type type = obj.GetType();
Console.WriteLine($"Logging: {type.Name}");
foreach (var prop in type.GetProperties())
{
var value = prop.GetValue(obj);
Console.WriteLine($"{prop.Name}: {value}");
}
}
Call like this:
var person = new Person { Name = "Ravi", Age = 25 };
LogObjectProperties(person);
Risks of Using Reflection
Let’s be clear – Reflection is powerful, but:
-
Slower than normal code (uses metadata lookup, dynamic invocation).
-
Breaks type safety (you don’t get compile-time errors).
-
Easy to misuse (e.g., accessing private members you shouldn’t touch).
-
Can introduce bugs that are hard to debug.
Use it when you really need dynamic behavior. Otherwise, go with regular, strongly-typed code.
When to Use (and Avoid) Reflection
Use when
-
You are writing a generic utility, serializer, mapper, etc.
-
You are building plugin architecture or dynamic loaders.
-
You are analyzing assemblies, types, attributes at runtime.
Avoid when
Summary
-
Reflection is about reading and interacting with the metadata of code at runtime.
-
You can create objects, get/set values, call methods, load assemblies, and even access private members.
-
It gives a lot of flexibility, but comes with performance cost and risk of runtime errors.
-
Use it only when needed, and keep performance and maintainability in mind.