C# Reflection Basics
Introduction
Reflection is a powerful feature in C# that allows your programs to examine and interact with their own structure and behavior at runtime. It provides a way to inspect assemblies, types, methods, properties, and other code elements dynamically, without knowing their names at compile time.
Think of reflection as your application holding up a mirror to see itself. This capability enables you to:
- Discover and use types in assemblies dynamically
- Create instances of types at runtime
- Access and modify properties and fields
- Invoke methods and constructors
- Generate new code on the fly
While reflection is an advanced topic, understanding its basics will open up new possibilities in your C# programming journey.
The System.Reflection Namespace
The core functionality for reflection in C# comes from the System.Reflection
namespace. To use reflection in your programs, you'll need to include:
using System.Reflection;
This namespace contains classes like Assembly
, Type
, MethodInfo
, PropertyInfo
, and more that are essential for reflection operations.
Getting Type Information
The foundation of reflection is the ability to obtain Type
information. There are several ways to do this:
Using typeof Operator
The most direct way to get a type is with the typeof
operator:
Type stringType = typeof(string);
Console.WriteLine($"Type: {stringType.Name}");
Console.WriteLine($"Full Name: {stringType.FullName}");
Console.WriteLine($"Is it a class? {stringType.IsClass}");
// Output:
// Type: String
// Full Name: System.String
// Is it a class? True
Using GetType() Method
If you already have an instance of an object, you can use the GetType()
method:
string message = "Hello, Reflection!";
Type messageType = message.GetType();
Console.WriteLine($"Type: {messageType.Name}");
Console.WriteLine($"Namespace: {messageType.Namespace}");
// Output:
// Type: String
// Namespace: System
Type.GetType Method
You can also get a type by its fully qualified string name:
Type dateTimeType = Type.GetType("System.DateTime");
Console.WriteLine($"Type: {dateTimeType.Name}");
Console.WriteLine($"Is it a struct? {dateTimeType.IsValueType}");
// Output:
// Type: DateTime
// Is it a struct? True
Examining Type Members
Once you have a Type
object, you can inspect its members—methods, properties, fields, events, etc.
Getting Methods
Type stringType = typeof(string);
MethodInfo[] methods = stringType.GetMethods();
Console.WriteLine($"String type has {methods.Length} methods:");
foreach (var method in methods.Take(5)) // Just show first 5 for brevity
{
Console.WriteLine($"- {method.Name}");
}
// Output:
// String type has 73 methods:
// - ToString
// - Equals
// - Equals
// - GetHashCode
// - Clone
Getting Properties
Type listType = typeof(List<int>);
PropertyInfo[] properties = listType.GetProperties();
Console.WriteLine($"List<int> type has {properties.Length} properties:");
foreach (var property in properties)
{
Console.WriteLine($"- {property.Name} ({property.PropertyType.Name})");
}
// Output:
// List<int> type has 2 properties:
// - Capacity (Int32)
// - Count (Int32)
Getting Constructors
Type dateTimeType = typeof(DateTime);
ConstructorInfo[] constructors = dateTimeType.GetConstructors();
Console.WriteLine($"DateTime has {constructors.Length} public constructors:");
foreach (var constructor in constructors)
{
Console.WriteLine($"- Constructor with {constructor.GetParameters().Length} parameters");
}
// Output:
// DateTime has 4 public constructors:
// - Constructor with 3 parameters
// - Constructor with 6 parameters
// - Constructor with 7 parameters
// - Constructor with 8 parameters
Creating Objects with Reflection
Reflection allows you to create objects dynamically:
// Get the type
Type listType = typeof(List<string>);
// Create an instance using Activator
List<string> myList = (List<string>)Activator.CreateInstance(listType);
// Use the object
myList.Add("Created via reflection");
Console.WriteLine($"List count: {myList.Count}");
Console.WriteLine($"List content: {myList[0]}");
// Output:
// List count: 1
// List content: Created via reflection
For types with constructor parameters:
// Get the DateTime type
Type dateTimeType = typeof(DateTime);
// Get a specific constructor (year, month, day)
ConstructorInfo constructor = dateTimeType.GetConstructor(new[] {
typeof(int), typeof(int), typeof(int)
});
// Create a DateTime for January 1, 2023
object date = constructor.Invoke(new object[] { 2023, 1, 1 });
Console.WriteLine($"Created date: {date}");
// Output:
// Created date: 1/1/2023 12:00:00 AM
Invoking Methods with Reflection
Reflection lets you call methods dynamically:
// Create a string
string message = "hello, reflection!";
// Get the type
Type stringType = typeof(string);
// Get the ToUpper method (no parameters)
MethodInfo toUpperMethod = stringType.GetMethod("ToUpper", new Type[0]);
// Invoke the method
string result = (string)toUpperMethod.Invoke(message, null);
Console.WriteLine($"Original: {message}");
Console.WriteLine($"After ToUpper(): {result}");
// Output:
// Original: hello, reflection!
// After ToUpper(): HELLO, REFLECTION!
Accessing and Modifying Properties
You can read and write properties through reflection:
// Create a List<int>
List<int> numbers = new List<int> { 1, 2, 3 };
// Get the type
Type listType = numbers.GetType();
// Get the Count property
PropertyInfo countProperty = listType.GetProperty("Count");
// Read the property value
int count = (int)countProperty.GetValue(numbers);
Console.WriteLine($"List count: {count}");
// Get the Capacity property
PropertyInfo capacityProperty = listType.GetProperty("Capacity");
// Read the current capacity
int currentCapacity = (int)capacityProperty.GetValue(numbers);
Console.WriteLine($"Current capacity: {currentCapacity}");
// Set the capacity to a new value
capacityProperty.SetValue(numbers, 10);
Console.WriteLine($"New capacity: {numbers.Capacity}");
// Output:
// List count: 3
// Current capacity: 4
// New capacity: 10
Practical Example: Creating a Simple Object Inspector
Let's create a practical example that uses reflection to inspect an object's properties:
public static void InspectObject(object obj)
{
if (obj == null)
{
Console.WriteLine("Object is null");
return;
}
Type type = obj.GetType();
Console.WriteLine($"Object Type: {type.Name}");
Console.WriteLine("Properties:");
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
try
{
object value = property.GetValue(obj);
string valueStr = value?.ToString() ?? "null";
Console.WriteLine($"- {property.Name}: {valueStr}");
}
catch (Exception ex)
{
Console.WriteLine($"- {property.Name}: [Error: {ex.Message}]");
}
}
}
// Example usage:
Person person = new Person
{
Name = "John Doe",
Age = 30,
Email = "[email protected]"
};
InspectObject(person);
// Output (assuming Person class with those properties):
// Object Type: Person
// Properties:
// - Name: John Doe
// - Age: 30
// - Email: [email protected]
This simple inspector can be used for debugging or diagnostic purposes, showing you the contents of any object at runtime.
Working with Attributes via Reflection
Reflection allows you to inspect custom attributes applied to types and members:
// Define a custom attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class VersionAttribute : Attribute
{
public string Version { get; }
public VersionAttribute(string version)
{
Version = version;
}
}
// Apply the attribute to a class
[Version("1.0.0")]
public class SampleService
{
[Version("1.0.1")]
public void DoSomething() { }
}
// Inspect the attributes
Type serviceType = typeof(SampleService);
VersionAttribute classAttr = (VersionAttribute)serviceType.GetCustomAttribute(typeof(VersionAttribute));
Console.WriteLine($"Class version: {classAttr.Version}");
MethodInfo method = serviceType.GetMethod("DoSomething");
VersionAttribute methodAttr = (VersionAttribute)method.GetCustomAttribute(typeof(VersionAttribute));
Console.WriteLine($"Method version: {methodAttr.Version}");
// Output:
// Class version: 1.0.0
// Method version: 1.0.1
Performance Considerations
While reflection is powerful, it comes with performance costs:
- Slower execution: Reflection operations are significantly slower than direct code
- Memory usage: Reflection can consume more memory due to metadata loading
- Type safety: Reflection bypasses compile-time type checking, which can lead to runtime errors
For performance-critical code, consider:
- Caching reflection results when possible
- Using alternatives like compiled expressions for frequently executed reflection code
- Using reflection only when necessary
Summary
Reflection is a powerful feature that allows C# programs to examine and modify their structure and behavior at runtime. In this tutorial, we've covered:
- Getting type information using
typeof
,GetType()
, andType.GetType()
- Examining type members such as methods, properties, and constructors
- Creating objects dynamically with reflection
- Invoking methods and accessing properties through reflection
- Working with attributes
- Performance considerations when using reflection
Reflection opens up many possibilities for building flexible, dynamic applications, but it should be used judiciously due to its performance impact and reduced type safety.
Additional Resources
Exercises
- Write a program that uses reflection to list all public methods of the
DateTime
struct. - Create a simple serializer that converts an object's properties to a dictionary using reflection.
- Write a method that can copy all property values from one object to another of a different type, where property names match.
- Build a simple dependency injection container using reflection to create and resolve instances.
- Create a custom attribute and write code that uses reflection to find all classes in your assembly with that attribute.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)