Reflection and Metaprogramming
Overview
Metaprogramming is a technique where a program manipulates or generates itself or other programs. .NET provides powerful metaprogramming features such as Reflection, Expression Trees, and the recently introduced Source Generators.
Reflection
Reflection allows you to inspect and manipulate assemblies, modules, types, and members at runtime.
Basic Usage
using System.Reflection;
public class Person
{
public string Name { get; set; }
public void SayHello() => Console.WriteLine($"Hello, {Name}!");
}
// Get type information
Type type = typeof(Person);
// or
// Person p = new Person();
// Type type = p.GetType();
// Create instance
object instance = Activator.CreateInstance(type);
// Set property
PropertyInfo prop = type.GetProperty("Name");
prop.SetValue(instance, "Alice");
// Invoke method
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(instance, null); // Output: Hello, Alice!
Performance Impact and Mitigation
Reflection is powerful but slower than normal code execution.
- Caching:
PropertyInfoandMethodInfohave high retrieval costs, so cache and reuse them. - Delegates: Use
Delegate.CreateDelegateto convert reflection calls into delegates for better performance.
Source Generators
Source Generators allow you to inspect code at compile time and generate new C# source files that are added to the compilation. Introduced in .NET 5.
Difference from Reflection
- Runtime vs Compile-time: Reflection resolves at runtime, whereas Source Generators generate code at compile time, resulting in no runtime overhead.
- AOT (Ahead-of-Time) Support: Since they don't rely on reflection, they are suitable for environments like Native AOT.
Use Cases
System.Text.Json: Faster JSON serializationILogger: Generation of high-performance logging methodsINotifyPropertyChanged: Automatic generation of boilerplate code
Expression Trees
Expression Trees represent code as a data structure (tree). This allows code to be inspected, modified, or compiled into executable code at runtime.
Basics
using System.Linq.Expressions;
// Treat lambda expression as data
Expression<Func<int, int, int>> addExpr = (a, b) => a + b;
// Compile into executable delegate
Func<int, int, int> addFunc = addExpr.Compile();
Console.WriteLine(addFunc(1, 2)); // 3
Usage in ORM
LINQ providers like Entity Framework Core use Expression Trees to translate C# query expressions into SQL.
Dynamic Type and ExpandoObject
dynamic type
Introduced in C# 4.0, the dynamic keyword allows you to defer type checking until runtime.
dynamic d = 1;
Console.WriteLine(d.GetType()); // System.Int32
d = "Hello";
Console.WriteLine(d.GetType()); // System.String
ExpandoObject
Using System.Dynamic.ExpandoObject, you can create objects where members can be dynamically added or removed at runtime.
dynamic person = new System.Dynamic.ExpandoObject();
person.Name = "Bob";
person.Age = 30;
// Can be cast to dictionary
var dict = (IDictionary<string, object>)person;
Console.WriteLine(dict["Name"]); // Bob
Best Practices
- Prefer Source Generators over Reflection: Use compile-time code generation whenever possible for better performance and AOT compatibility.
- Cache Reflection Results: Since type and member information rarely changes, cache retrieved
MethodInfoorPropertyInfoin static fields or dictionaries. - Minimize use of
dynamic: Avoid it unless necessary (e.g., COM interoperability, dynamic JSON processing) as you lose static typing safety and IDE support (like IntelliSense). - Consider Security: Accessing private members via reflection is possible but can introduce security risks and future compatibility issues, so do it cautiously.
Summary
- Reflection: Highly versatile but requires attention to performance.
- Source Generators: Perform code generation at compile time, excelling in performance and AOT support. Often recommended as a replacement for reflection in modern .NET development.
- Expression Trees: Essential for LINQ providers and dynamic query construction.
- Dynamic: Since you lose the benefits of static typing, use it only when necessary, such as for COM interoperability or working with dynamic languages.