Reflection and Late Binding in C#
Learn Reflection and late binding in C# to inspect types at runtime and build flexible, dynamic applications.
In C#, Reflection lets you access type information at runtime, operate on types, and create objects dynamically. Late Binding refers to accessing objects at runtime whose types are unknown at compile time. Using these two mechanisms, you can build flexible, plugin-based, or dynamically modular systems.
What Is Reflection?
Reflection is a powerful mechanism that enables inspecting and using types (class, method, property, field, etc.) in an assembly at runtime. For example, you can view classes, methods, attributes, or their values inside an assembly file.
using System;
using System.Reflection;
class Example
{
public int Number { get; set; }
public void Write() => Console.WriteLine("Write() method called.");
}
class Program
{
static void Main()
{
Type type = typeof(Example);
Console.WriteLine($"Class name: {type.Name}");
// List properties
foreach (var prop in type.GetProperties())
Console.WriteLine($"Property: {prop.Name}");
// List methods
foreach (var method in type.GetMethods())
Console.WriteLine($"Method: {method.Name}");
}
}
In this example, the Type object provides access to all information about the class structure.
You can perform detailed inspection with methods like GetMethods(), GetProperties(), and GetFields().
What Is Late Binding?
Late Binding means creating an instance of a class at runtime when its type is unknown at compile time. Using reflection, you load the class, create the object, and invoke its methods dynamically. It’s commonly used in plugin systems, loading external DLLs, and dynamic type management.
using System;
using System.Reflection;
class Greeter
{
public void Greet(string name)
{
Console.WriteLine($"Hello, {name}!");
}
}
class Program
{
static void Main()
{
// Create an object at runtime using type information
Type type = Type.GetType("Greeter");
object instance = Activator.CreateInstance(type);
// Invoke a method (Late Binding)
MethodInfo method = type.GetMethod("Greet");
method.Invoke(instance, new object[] { "Ahmed" });
}
}
Here, the Greeter class is loaded at runtime by its name string.
Because its type wasn’t known at compile time, this is called late binding.
Dynamic Loading via Assembly
With Assembly.LoadFrom() or Assembly.Load(), you can load an external DLL and discover its types.
This approach is frequently used for plugin or modular architectures.
using System;
using System.Reflection;
class Program
{
static void Main()
{
// Load external DLL
Assembly dll = Assembly.LoadFrom("MyLibrary.dll");
// List types
foreach (var type in dll.GetTypes())
Console.WriteLine($"Type: {type.FullName}");
// Create an instance from a type and invoke a method
Type targetType = dll.GetType("MyLibrary.MathHelper");
object instance = Activator.CreateInstance(targetType);
MethodInfo method = targetType.GetMethod("Add");
object result = method.Invoke(instance, new object[] { 3, 5 });
Console.WriteLine($"Sum result: {result}");
}
}
With this method, external libraries can be loaded at runtime, and their types and methods can be used dynamically.
Dynamic Access to Property and Field Values
Reflection isn’t limited to invoking methods; you can also access and even modify property and field values.
using System;
using System.Reflection;
class Car
{
public string Brand { get; set; } = "Ford";
private int Speed = 120;
}
class Program
{
static void Main()
{
var car = new Car();
Type type = car.GetType();
// Read public property
var prop = type.GetProperty("Brand");
Console.WriteLine("Brand: " + prop.GetValue(car));
// Access private field
var field = type.GetField("Speed", BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine("Speed: " + field.GetValue(car));
// Change value
field.SetValue(car, 200);
Console.WriteLine("New Speed: " + field.GetValue(car));
}
}
By using BindingFlags, you can control access to public, private, or static members.
Accessing Attribute Information
Reflection also allows accessing Attribute information defined on classes or members.
This feature is widely used in metadata-driven libraries (e.g., ORMs, serializers).
using System;
[AttributeUsage(AttributeTargets.Class)]
class InfoAttribute : Attribute
{
public string Description { get; }
public InfoAttribute(string description) => Description = description;
}
[Info("This class is for demonstration purposes.")]
class Example { }
class Program
{
static void Main()
{
Type type = typeof(Example);
var attr = (InfoAttribute)Attribute.GetCustomAttribute(type, typeof(InfoAttribute));
Console.WriteLine($"Description: {attr.Description}");
}
}
This mechanism lets you attach metadata to classes and read that information at runtime.
Performance and Security Considerations
- Reflection is powerful but slow — it can be 10–100× more expensive than a direct method call.
- In performance-critical scenarios, cache previously discovered
MethodInfoobjects. - Reflection can access private members, so security restrictions are important.
dynamicorExpression<T>-based alternatives may be more efficient in some cases.
Example: Plugin System
Reflection is commonly used to build plugin or modular systems.
The example below finds classes implementing the IPlugin interface inside a DLL and loads them dynamically.
using System;
using System.Linq;
using System.Reflection;
public interface IPlugin
{
string Name { get; }
void Execute();
}
class Program
{
static void Main()
{
Assembly dll = Assembly.LoadFrom("PluginPack.dll");
var pluginTypes = dll.GetTypes().Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface);
foreach (var type in pluginTypes)
{
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);
Console.WriteLine($"Plugin: {plugin.Name}");
plugin.Execute();
}
}
}
With this setup, new plugins can be incorporated at runtime even if they were unknown at compile time.
TL;DR
- Reflection: Provides runtime type information and dynamic access (
Type,MethodInfo,PropertyInfo, etc.). - Late Binding: Loading and using classes at runtime that are unknown at compile time.
- External DLLs can be loaded and invoked dynamically with
Assembly.LoadFrom(). - Use
BindingFlagsto access private members. - It has a high performance cost — use only when necessary.
- Common in real-world plugins, serializers, ORMs, and testing frameworks.
Related Articles
Class, Object, Property and Methods in C#
Learn how classes, objects, properties, and methods work in C# and form the core building blocks of object-oriented programming.
Code Analysis with Roslyn Compiler API in C#
Learn code analysis in C# using Roslyn Compiler API, including syntax trees, analyzers, and code generation scenarios.
Design Patterns in C# (Factory, Singleton, Repository, Observer)
Learn design patterns in C#, including Factory, Singleton, Repository, and Observer, to build flexible and maintainable applications.
Interfaces and Abstract Classes in C#
Learn interfaces and abstract classes in C#, their differences, and when to use each approach to design clean and extensible code.
SOLID Principles with C#
Applying SOLID principles in C# with examples: building flexible, maintainable, and testable code.
The Concept of Source Generators in C# (C# 9+)
Learn source generators in C# to generate code at compile time and improve performance with modern development techniques.