Advanced .NET Reflection
Reflection in .NET provides the ability to inspect and manipulate metadata and code at runtime. This is a powerful technique that allows you to dynamically examine types, members, and assemblies. This section delves into advanced concepts and practical applications of reflection.
Understanding the Core Concepts
Reflection is primarily facilitated by types in the System.Reflection
namespace. Key types include:
Assembly
: Represents an assembly, which is a collection of types and resources that are used to make up a .NET application.Type
: Represents type declarations (classes, interfaces, arrays, value types, enumerations, etc.) at runtime.MethodInfo
: Provides information about a method and provides access to method metadata.PropertyInfo
: Provides information about a property and provides access to property metadata.FieldInfo
: Provides information about a field and provides access to field metadata.ConstructorInfo
: Provides information about a constructor and provides access to constructor metadata.EventInfo
: Provides information about an event and provides access to event metadata.
Dynamic Invocation of Members
One of the most common uses of reflection is to dynamically invoke methods and access properties/fields. This is especially useful in scenarios like serialization, ORM frameworks, and plugin architectures.
Invoking a Method Dynamically
To invoke a method, you first need to get a MethodInfo
object representing the method you want to call. Then, you can use the Invoke
method.
using System;
using System.Reflection;
public class MyClass
{
public void Greet(string name)
{
Console.WriteLine($"Hello, {name}!");
}
}
// ... in another part of your code ...
Type type = typeof(MyClass);
object instance = Activator.CreateInstance(type);
MethodInfo method = type.GetMethod("Greet");
if (method != null)
{
object[] parameters = { "World" };
method.Invoke(instance, parameters); // Output: Hello, World!
}
Accessing Properties Dynamically
using System;
using System.Reflection;
public class MyData
{
public string Name { get; set; }
}
// ...
Type dataType = typeof(MyData);
object dataInstance = Activator.CreateInstance(dataType);
PropertyInfo nameProperty = dataType.GetProperty("Name");
if (nameProperty != null)
{
nameProperty.SetValue(dataInstance, "Reflection Example");
object value = nameProperty.GetValue(dataInstance);
Console.WriteLine($"Name: {value}"); // Output: Name: Reflection Example
}
Working with Attributes
Attributes provide a way to add declarative information to your code. Reflection allows you to discover and use these attributes at runtime.
Retrieving Attributes from a Type
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomDocumentationAttribute : Attribute
{
public string Description { get; }
public CustomDocumentationAttribute(string description)
{
Description = description;
}
}
[CustomDocumentation("A sample class for demonstration.")]
public class DocumentedClass
{
[CustomDocumentation("A sample method.")]
public void DoSomething() { }
}
// ...
Type docType = typeof(DocumentedClass);
CustomDocumentationAttribute classAttr = (CustomDocumentationAttribute)Attribute.GetCustomAttribute(docType, typeof(CustomDocumentationAttribute));
if (classAttr != null)
{
Console.WriteLine($"Class Description: {classAttr.Description}"); // Output: Class Description: A sample class for demonstration.
}
MethodInfo methodInfo = docType.GetMethod("DoSomething");
CustomDocumentationAttribute methodAttr = (CustomDocumentationAttribute)Attribute.GetCustomAttribute(methodInfo, typeof(CustomDocumentationAttribute));
if (methodAttr != null)
{
Console.WriteLine($"Method Description: {methodAttr.Description}"); // Output: Method Description: A sample method.
}
Advanced Techniques
- Late Binding: Invoking members without knowing their exact type at compile time.
- Assembly Loading: Dynamically loading assemblies from disk or from memory.
- Reflection Emit: Generating and executing IL (Intermediate Language) code dynamically at runtime. This is advanced and typically used for Just-In-Time (JIT) compilation or code generation.
- Expression Trees: While not strictly reflection, expression trees can be compiled into executable code and offer a more performant way to build dynamic queries and operations, often used as an alternative to reflection for specific tasks.
Dynamic Assembly Loading
using System;
using System.Reflection;
// Assume "MyExternalAssembly.dll" exists and contains a class named "ExternalClass"
// with a public method "Execute".
try
{
Assembly externalAssembly = Assembly.LoadFrom("MyExternalAssembly.dll");
Type externalType = externalAssembly.GetType("MyExternalAssembly.ExternalClass");
if (externalType != null)
{
object externalInstance = Activator.CreateInstance(externalType);
MethodInfo executeMethod = externalType.GetMethod("Execute");
if (executeMethod != null)
{
executeMethod.Invoke(externalInstance, null);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error loading or executing assembly: {ex.Message}");
}
Performance Considerations
Reflection involves significant overhead because it bypasses the compiler's optimizations. Operations like:
- Getting type metadata.
- Finding members.
- Invoking methods.
- Accessing properties/fields.
are all computationally more expensive than direct code execution. When performance is critical, consider these alternatives:
- Caching reflected information (e.g., storing
MethodInfo
objects). - Using expression trees for dynamic member access and invocation.
- Implementing interfaces or abstract base classes if the set of operations is known.
Use Cases
- Serialization/Deserialization: Libraries like JSON.NET and XmlSerializer heavily rely on reflection to read and write object properties.
- Object-Relational Mappers (ORMs): Entity Framework and Dapper use reflection to map database columns to object properties.
- Dependency Injection Containers: These frameworks use reflection to discover and instantiate types.
- Plugin Architectures: Loading and executing code from external assemblies dynamically.
- Unit Testing Frameworks: Discovering and running test methods.
Type
and MemberInfo
objects to reduce repeated lookups.