What is reflection and what are its use cases and drawbacks?

12 minadvanced.NETreflectionmetadata

Quick Answer

Reflection allows inspecting and interacting with type metadata at runtime. Use cases include serialization, dependency injection, attribute-based programming, and plugin systems. Drawbacks include performance overhead (10-100x slower), loss of type safety, security risks, and maintenance issues. Use alternatives like expression trees or source generators when possible.

Detailed Answer

Reflection is the ability to inspect and interact with metadata of types, assemblies, methods, and properties at runtime.

What Reflection Allows:

  • Inspect types, methods, properties, and fields at runtime
  • Create instances of types dynamically
  • Invoke methods and access properties dynamically
  • Read attributes and metadata
  • Generate code at runtime

Common Use Cases:

  1. Serialization/Deserialization:
// JSON serialization uses reflection to read properties
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Person person = new Person { Name = "John", Age = 30 };
string json = JsonSerializer.Serialize(person);  // Uses reflection internally
  1. Dependency Injection:
// DI containers use reflection to create instances
services.AddTransient<IUserService, UserService>();
// Container uses reflection to find constructor and inject dependencies
  1. Attribute-Based Programming:
[Route("api/users")]
public class UsersController : ControllerBase
{
    [HttpGet("{id}")]
    [Authorize(Roles = "Admin")]
    public IActionResult GetUser(int id)
    {
        // Framework uses reflection to read Route and Authorize attributes
    }
}
  1. Plugin Systems:
// Load plugins dynamically
Assembly assembly = Assembly.LoadFrom("MyPlugin.dll");
Type pluginType = assembly.GetType("MyPlugin.PluginClass");
IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);
plugin.Execute();

Reflection Examples:

Type Inspection:

Type type = typeof(Person);

// Get properties
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
    Console.WriteLine($"{prop.Name}: {prop.PropertyType}");
}

// Get methods
MethodInfo[] methods = type.GetMethods();

// Check if type implements interface
bool implementsInterface = typeof(IDisposable).IsAssignableFrom(type);

// Get custom attributes
var attributes = type.GetCustomAttributes(typeof(ObsoleteAttribute), false);

Dynamic Instantiation:

// Create instance without new keyword
Type type = typeof(Person);
object instance = Activator.CreateInstance(type);

// With constructor parameters
object instance = Activator.CreateInstance(type, "John", 30);

// Set property value
PropertyInfo nameProp = type.GetProperty("Name");
nameProp.SetValue(instance, "Jane");

// Get property value
object value = nameProp.GetValue(instance);

Dynamic Method Invocation:

public class Calculator
{
    public int Add(int a, int b) => a + b;
    public int Multiply(int a, int b) => a * b;
}

// Invoke method dynamically
Calculator calc = new Calculator();
Type type = typeof(Calculator);
MethodInfo method = type.GetMethod("Add");

// Invoke with parameters
object result = method.Invoke(calc, new object[] { 5, 3 });
Console.WriteLine(result);  // 8

Reading Attributes:

[AttributeUsage(AttributeTargets.Property)]
public class ValidationAttribute : Attribute
{
    public int MaxLength { get; set; }
    public bool Required { get; set; }
}

public class User
{
    [Validation(MaxLength = 50, Required = true)]
    public string Name { get; set; }
    
    [Validation(MaxLength = 100)]
    public string Email { get; set; }
}

// Read attributes using reflection
Type type = typeof(User);
foreach (PropertyInfo prop in type.GetProperties())
{
    var attr = prop.GetCustomAttribute<ValidationAttribute>();
    if (attr != null)
    {
        Console.WriteLine($"{prop.Name}: MaxLength={attr.MaxLength}, Required={attr.Required}");
    }
}

Generic Type Creation:

// Create List<int> dynamically
Type listType = typeof(List<>);
Type genericListType = listType.MakeGenericType(typeof(int));
object listInstance = Activator.CreateInstance(genericListType);

MethodInfo addMethod = genericListType.GetMethod("Add");
addMethod.Invoke(listInstance, new object[] { 42 });

Drawbacks and Limitations:

  1. Performance:
// Direct call
person.Name = "John";  // Fast

// Reflection call
PropertyInfo prop = typeof(Person).GetProperty("Name");
prop.SetValue(person, "John");  // 10-100x slower!
  1. Type Safety:
// Compile-time error - caught immediately
// person.NonExistentProperty = "value";  // Won't compile

// Runtime error - only caught when executed
PropertyInfo prop = type.GetProperty("NonExistentProperty");  // Returns null
prop.SetValue(person, "value");  // NullReferenceException at runtime!
  1. Security Risks:
// Can access private members!
Type type = typeof(Person);
FieldInfo privateField = type.GetField("secretKey", 
    BindingFlags.NonPublic | BindingFlags.Instance);
privateField.SetValue(person, "hacked");  // Breaks encapsulation!
  1. Code Maintenance:
// Refactoring tools can't update string-based references
MethodInfo method = type.GetMethod("OldMethodName");
// If method is renamed, this code breaks at runtime, not compile-time

Performance Comparison:

// Benchmark results (approximate)
// Direct call: 1 ns
// Cached reflection: 10-50 ns
// Uncached reflection: 100-1000 ns
// Expression trees: 5-10 ns

Better Alternatives:

1. Expression Trees (faster than reflection):

// Compile expression once, reuse many times
public static class PropertyAccessor<T>
{
    private static readonly Dictionary<string, Func<T, object>> Getters = new();
    
    public static object GetValue(T obj, string propertyName)
    {
        if (!Getters.ContainsKey(propertyName))
        {
            var param = Expression.Parameter(typeof(T));
            var prop = Expression.Property(param, propertyName);
            var convert = Expression.Convert(prop, typeof(object));
            var lambda = Expression.Lambda<Func<T, object>>(convert, param);
            Getters[propertyName] = lambda.Compile();
        }
        
        return Getters[propertyName](obj);
    }
}

2. Source Generators (C# 9+):

// Generate code at compile-time instead of runtime reflection
[AutoMapper]
public partial class PersonDto
{
    public string Name { get; set; }
    // Source generator creates mapping code at compile-time
}

When to Use Reflection:

  • Building frameworks or libraries (DI, ORM, serialization)
  • Plugin architectures
  • Unit testing tools
  • Code generation tools
  • When dynamic behavior is absolutely necessary

When NOT to Use Reflection:

  • Performance-critical code paths
  • When compile-time checking is important
  • When simpler alternatives exist (interfaces, delegates, generics)
  • Hot paths in loops

Best Practices:

// Cache reflection results
private static readonly PropertyInfo NameProperty = typeof(Person).GetProperty("Name");

// Use binding flags to be specific
type.GetMethod("MyMethod", BindingFlags.Public | BindingFlags.Instance);

// Check for null before using
PropertyInfo prop = type.GetProperty("Name");
if (prop != null)
{
    // Safe to use
}

// Use reflection only when necessary
// Prefer: interfaces, generics, delegates

Related Resources