Understanding Reflection in .NET
Reflection lets you inspect types, methods, and properties at runtime without knowing them at compile time. You'll use it when building frameworks, serializers, dependency injection containers, or plugin systems that work with types dynamically.
The System.Reflection namespace provides classes like Type, MethodInfo, PropertyInfo, and FieldInfo that let you examine and manipulate code metadata. While powerful, reflection comes with performance costs, so you'll want to use it thoughtfully.
You'll learn how to inspect types, invoke methods dynamically, access properties and fields, and apply these techniques to real-world scenarios while avoiding common pitfalls.
Inspecting Types and Members
The Type class is your starting point for reflection. You can get type information using typeof, GetType(), or Type.GetType() with a string name. Once you have a Type object, you can explore its members.
using System;
using System.Reflection;
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
private decimal price;
public void UpdatePrice(decimal newPrice)
{
price = newPrice;
}
}
public class ReflectionExample
{
public static void InspectType()
{
// Get type information
Type productType = typeof(Product);
Console.WriteLine($"Type Name: {productType.Name}");
Console.WriteLine($"Full Name: {productType.FullName}");
Console.WriteLine($"Namespace: {productType.Namespace}");
// Get properties
PropertyInfo[] properties = productType.GetProperties();
Console.WriteLine("\nPublic Properties:");
foreach (var prop in properties)
{
Console.WriteLine($" {prop.Name} ({prop.PropertyType.Name})");
}
// Get methods
MethodInfo[] methods = productType.GetMethods(
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
Console.WriteLine("\nPublic Methods:");
foreach (var method in methods)
{
Console.WriteLine($" {method.Name}");
}
// Get fields (including private)
FieldInfo[] fields = productType.GetFields(
BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine("\nPrivate Fields:");
foreach (var field in fields)
{
Console.WriteLine($" {field.Name}");
}
}
}
BindingFlags control which members you retrieve. Combine flags like Public, NonPublic, Instance, and Static to filter members. DeclaredOnly excludes inherited members.
Invoking Methods Dynamically
Once you have method information, you can invoke methods on objects without knowing the method names at compile time. This is useful for plugin systems or when executing commands based on user input.
public class Calculator
{
public int Add(int a, int b) => a + b;
public int Multiply(int a, int b) => a * b;
public double Divide(double a, double b) => a / b;
}
public class DynamicMethodCaller
{
public static void CallMethodByName(string methodName)
{
var calculator = new Calculator();
Type type = calculator.GetType();
// Get method by name
MethodInfo method = type.GetMethod(methodName);
if (method == null)
{
Console.WriteLine($"Method {methodName} not found");
return;
}
// Invoke with parameters
object[] parameters = { 10, 5 };
object result = method.Invoke(calculator, parameters);
Console.WriteLine($"{methodName}(10, 5) = {result}");
}
public static void CallAllMethods()
{
var calculator = new Calculator();
Type type = typeof(Calculator);
// Get all public methods
MethodInfo[] methods = type.GetMethods(
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (var method in methods)
{
// Check parameter count
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length == 2)
{
object[] args = { 20, 4 };
object result = method.Invoke(calculator, args);
Console.WriteLine($"{method.Name}: {result}");
}
}
}
}
// Usage
DynamicMethodCaller.CallMethodByName("Add");
DynamicMethodCaller.CallAllMethods();
Method invocation requires matching parameter types. If types don't match, you'll get exceptions. Always validate parameters before invoking methods dynamically.
Accessing Properties and Fields
Reflection lets you read and write property values dynamically. This is the foundation of serialization libraries and object mappers that convert between different data formats.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
public class PropertyHelper
{
public static void CopyProperties(object source, object destination)
{
Type sourceType = source.GetType();
Type destType = destination.GetType();
PropertyInfo[] sourceProperties = sourceType.GetProperties();
foreach (var sourceProp in sourceProperties)
{
// Find matching property in destination
PropertyInfo destProp = destType.GetProperty(sourceProp.Name);
if (destProp != null && destProp.CanWrite)
{
// Get value from source
object value = sourceProp.GetValue(source);
// Set value in destination
destProp.SetValue(destination, value);
}
}
}
public static void PrintObjectProperties(object obj)
{
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
Console.WriteLine($"Properties of {type.Name}:");
foreach (var prop in properties)
{
object value = prop.GetValue(obj);
Console.WriteLine($" {prop.Name}: {value}");
}
}
}
// Usage
var person1 = new Person { FirstName = "John", LastName = "Doe", Age = 30 };
var person2 = new Person();
PropertyHelper.CopyProperties(person1, person2);
PropertyHelper.PrintObjectProperties(person2);
Working with Attributes
Attributes provide metadata about types and members. Reflection lets you read these attributes at runtime, enabling declarative programming patterns used by frameworks like ASP.NET Core and Entity Framework.
using System;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
public class User
{
[Required]
[StringLength(50)]
public string Username { get; set; }
[EmailAddress]
public string Email { get; set; }
[Range(18, 100)]
public int Age { get; set; }
}
public class AttributeValidator
{
public static bool ValidateObject(object obj)
{
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
bool isValid = true;
foreach (var prop in properties)
{
// Get all attributes on this property
object[] attributes = prop.GetCustomAttributes(true);
foreach (var attr in attributes)
{
if (attr is RequiredAttribute)
{
var value = prop.GetValue(obj);
if (value == null || string.IsNullOrEmpty(value.ToString()))
{
Console.WriteLine($"{prop.Name} is required");
isValid = false;
}
}
if (attr is StringLengthAttribute stringLength)
{
var value = prop.GetValue(obj)?.ToString();
if (value?.Length > stringLength.MaximumLength)
{
Console.WriteLine(
$"{prop.Name} exceeds max length of {stringLength.MaximumLength}");
isValid = false;
}
}
}
}
return isValid;
}
}
// Usage
var user = new User { Username = "john_doe", Email = "invalid-email", Age = 25 };
bool isValid = AttributeValidator.ValidateObject(user);
GetCustomAttributes returns all attributes applied to a member. You can filter by attribute type or get all attributes and check their types manually.
Performance and Best Practices
Reflection is powerful but slower than direct code. Cache reflection results whenever possible to minimize performance impact.
using System.Collections.Concurrent;
public static class ReflectionCache
{
private static readonly ConcurrentDictionary<Type, PropertyInfo[]>
PropertyCache = new();
private static readonly ConcurrentDictionary<string, MethodInfo>
MethodCache = new();
public static PropertyInfo[] GetProperties(Type type)
{
return PropertyCache.GetOrAdd(type, t => t.GetProperties());
}
public static MethodInfo GetMethod(Type type, string methodName)
{
string key = $"{type.FullName}.{methodName}";
return MethodCache.GetOrAdd(key, _ => type.GetMethod(methodName));
}
}
// Usage - much faster on repeated calls
Type type = typeof(Product);
PropertyInfo[] props = ReflectionCache.GetProperties(type);
MethodInfo method = ReflectionCache.GetMethod(type, "UpdatePrice");
Additional tips: Use compiled expressions for repeated invocations. Consider source generators as alternatives when you know types at compile time. Avoid reflection in hot paths where performance matters most.