In the realm of .NET development, Reflection stands as a potent capability, empowering applications to inspect and manipulate their own metadata at runtime. This mechanism allows programs to discover information about types, their members (properties, methods, fields, constructors), and even dynamically create instances or invoke members that were not known at compile time.
What is Reflection?
Reflection is a feature of the .NET Framework and .NET Core that provides objects (of type System.Type) that describe assemblies, modules, and types. It can be used to:
- Examine assembly metadata to discover contained types and their members.
- Dynamically create instances of types.
- Invoke methods or access properties and fields on objects at runtime.
- Inspect and manipulate private members of a class.
Key Use Cases for Reflection
The power of Reflection is leveraged in various advanced scenarios within the .NET ecosystem:
- Object-Relational Mappers (ORMs): ORMs like Entity Framework utilize reflection to map database columns to object properties, dynamically generating queries and materializing objects from data.
- Serialization and Deserialization: Frameworks for JSON or XML serialization (e.g., Newtonsoft.Json,
System.Text.Json) inspect object structures using reflection to convert them into a stream of bytes or text, and vice-versa. - Dependency Injection (DI) Containers: DI frameworks (e.g., Autofac, Microsoft.Extensions.DependencyInjection) employ reflection to discover constructor dependencies and instantiate objects automatically, managing the object lifecycle.
- Dynamic Proxy Generation: Tools for aspect-oriented programming (AOP) or mocking frameworks often use reflection to create proxy objects that intercept method calls, enabling cross-cutting concerns or test doubles.
- Unit Testing Frameworks: Test frameworks can use reflection to access and test private members of classes, ensuring comprehensive code coverage.
- Plugin Architectures: Applications designed to load and execute external modules or plugins often rely on reflection to discover types and methods within dynamically loaded assemblies.
Core Reflection Classes
The primary types involved in reflection operations reside within the System.Reflection namespace:
Type: The fundamental class representing a type declaration. It's the entry point for most reflection operations.Assembly: Represents an assembly, which is the basic unit of deployment and versioning in .NET.MethodInfo: Provides information about and access to methods.PropertyInfo: Enables discovery of and interaction with properties.FieldInfo: Represents and allows manipulation of fields.ConstructorInfo: Allows introspection and invocation of constructors.
Practical Reflection Examples
Let's illustrate reflection with some C# code examples, demonstrating how to dynamically create objects and interact with their members, including private ones.
using System;
using System.Reflection;
public class WorkerProfile
{
private string employeeId;
private int yearsOfService;
public string FullName { get; set; }
public DateTime HireDate { get; private set; } // Private setter
public WorkerProfile(string name, DateTime hireDate)
{
FullName = name;
HireDate = hireDate;
employeeId = Guid.NewGuid().ToString().Substring(0, 8).ToUpper();
yearsOfService = DateTime.Now.Year - HireDate.Year;
}
private string GenerateInternalIdentifier(string prefix)
{
return $"{prefix}-{employeeId}";
}
public void OutputWorkerDetails()
{
Console.WriteLine($"+--- Worker Details ---+");
Console.WriteLine($"| Name: {FullName}");
Console.WriteLine($"| ID: {employeeId}");
Console.WriteLine($"| Hired: {HireDate.ToShortDateString()}");
Console.WriteLine($"+----------------------+");
}
}
public class ReflectionShowcase
{
public static void Main(string[] args)
{
Console.WriteLine("--- Dynamic Object Creation and Method Invocation ---");
ShowcaseDynamicInstantiationAndInvocation();
Console.WriteLine("\n--- Dynamic Member Access (Properties and Fields) ---");
ShowcaseMemberAccess();
}
static void ShowcaseDynamicInstantiationAndInvocation()
{
// 1. Obtain the Type object for WorkerProfile
Type workerType = typeof(WorkerProfile);
// 2. Locate a constructor with specific parameter types
ConstructorInfo constructor = workerType.GetConstructor(new Type[] { typeof(string), typeof(DateTime) });
if (constructor == null)
{
Console.WriteLine("Error: Matching constructor not found.");
return;
}
// 3. Dynamically create an instance using the found constructor
object workerInstance = constructor.Invoke(new object[] { "Alice Smith", new DateTime(2020, 5, 15) });
Console.WriteLine($"Successfully created instance of {workerType.Name}.");
// 4. Invoke a public method
MethodInfo publicMethod = workerType.GetMethod("OutputWorkerDetails");
publicMethod?.Invoke(workerInstance, null); // Invoke without arguments
// 5. Invoke a private method (requires BindingFlags)
MethodInfo privateMethod = workerType.GetMethod("GenerateInternalIdentifier", BindingFlags.NonPublic | BindingFlags.Instance);
if (privateMethod != null)
{
object[] methodArgs = { "INTERNAL-ACC" };
string generatedId = (string)privateMethod.Invoke(workerInstance, methodArgs);
Console.WriteLine($"Dynamically invoked private method. Generated ID: {generatedId}");
}
else
{
Console.WriteLine("Private method 'GenerateInternalIdentifier' not found.");
}
}
static void ShowcaseMemberAccess()
{
// Create an instance for demonstration
WorkerProfile existingWorker = new WorkerProfile("Bob Johnson", new DateTime(2018, 1, 10));
Type workerType = existingWorker.GetType(); // Get type from an existing instance
Console.WriteLine($"Initial Worker Name: {existingWorker.FullName}");
// Access and modify a public property (FullName)
PropertyInfo fullNameProperty = workerType.GetProperty("FullName");
if (fullNameProperty != null)
{
fullNameProperty.SetValue(existingWorker, "Robert Johnson", null);
Console.WriteLine($"Updated Name (via Reflection): {existingWorker.FullName}");
}
// Access a public property with a private setter (HireDate)
PropertyInfo hireDateProperty = workerType.GetProperty("HireDate");
if (hireDateProperty != null)
{
Console.WriteLine($"Hire Date: {hireDateProperty.GetValue(existingWorker, null):yyyy-MM-dd}");
// Attempting to set HireDate directly would fail if setter is private
// To set private setter, you'd need BindingFlags.NonPublic | BindingFlags.SetProperty
// E.g., hireDateProperty.SetValue(existingWorker, newDate, BindingFlags.NonPublic | BindingFlags.SetProperty, null, null, null);
}
// Access and modify a private field (yearsOfService)
FieldInfo yearsOfServiceField = workerType.GetField("yearsOfService", BindingFlags.NonPublic | BindingFlags.Instance);
if (yearsOfServiceField != null)
{
Console.WriteLine($"Original Years of Service (private field): {yearsOfServiceField.GetValue(existingWorker)}");
yearsOfServiceField.SetValue(existingWorker, 6); // Update the private field
Console.WriteLine($"Updated Years of Service (private field): {yearsOfServiceField.GetValue(existingWorker)}");
}
else
{
Console.WriteLine("Private field 'yearsOfService' not found.");
}
}
}