In C#, reflection is a powerful feature that allows you to inspect and manipulate types at runtime. It enables you to explore metadata about objects, classes, and other entities in your code without having to know them at compile time. While reflection can be useful for a variety of purposes—from dynamic object creation to testing and debugging—it must also be used with caution, as it can impact performance and security. This tutorial will guide you through the essential concepts, use cases, and practical examples of using reflection in C#.
What is Reflection?
Reflection is the ability of a program to inspect and interact with its own structure, metadata, and code during runtime. In C#, reflection is implemented through the classes in the System.Reflection
namespace. With reflection, you can:
- Discover Type Information: You can examine metadata such as class names, methods, properties, fields, and events of a type.
- Invoke Methods Dynamically: Reflection allows you to invoke methods on objects dynamically, even if the method names and parameters are not known until runtime.
- Access Private Members: It can be used to access private fields and methods, which might otherwise be inaccessible through traditional object-oriented programming techniques.
- Create Instances Dynamically: You can instantiate objects at runtime, even if the exact type isn’t known at compile time.
While reflection opens the door to dynamic behaviors and advanced use cases, its cost in terms of performance and security should not be overlooked. Excessive use of reflection can slow down your application, and accessing private members can lead to fragile code.
When to Use Reflection
Reflection is most useful in scenarios where the type information isn’t available at compile time. Some of the most common scenarios include:
- Dynamic loading of assemblies: If your application needs to load external assemblies at runtime (for example, plugins or modules), reflection allows you to discover types and methods in the loaded assembly.
- Object serialization and deserialization: Some frameworks use reflection to automatically serialize or deserialize objects, even if the object types are not known ahead of time.
- Testing and debugging: Unit testing frameworks like NUnit and MSTest use reflection to discover test methods.
- Framework or library development: If you’re building a library that interacts with other code dynamically, reflection can be helpful for inspecting and modifying objects.
Despite its versatility, reflection is typically avoided in performance-critical sections of the code. Modern development practices suggest relying on static type information whenever possible to avoid the overhead reflection may introduce.
Basic Concepts of Reflection in C
Before diving into practical examples, let’s discuss a few key concepts that are fundamental to understanding how reflection works in C#.
Assemblies
An assembly is the building block of .NET applications. It’s a compiled unit that can contain types, resources, and metadata. Assemblies can be either executable (.exe) or libraries (.dll), and they store all the code and metadata needed for an application to run.
Using reflection, you can load and inspect assemblies at runtime using methods provided by the System.Reflection
namespace.
Types
A Type
in .NET represents any data structure, including classes, interfaces, enums, arrays, and structs. The System.Type
class is the key entry point for inspecting types in reflection. Through a Type
object, you can discover details about a class, such as its name, methods, fields, properties, events, base class, and implemented interfaces.
Members
Members of a class include methods, fields, properties, events, and constructors. Reflection allows you to inspect and interact with these members using methods like GetMethods()
, GetFields()
, GetProperties()
, etc.
Accessing Type Information with Reflection
In C#, the primary class for working with reflection is System.Type
. This class provides the basic functionality you need to inspect types. To obtain a Type
object, you have several options:
- Use the
typeof()
operator, which returns theType
object for a known type at compile time. - Call
GetType()
on an instance of an object to obtain its runtime type. - Use
Assembly.GetType()
to load a type by its name at runtime from an assembly.
Here are examples of each approach:
using System;
class Program
{
static void Main()
{
// Option 1: Using typeof operator
Type stringType = typeof(string);
Console.WriteLine(stringType.FullName);
// Option 2: Using GetType on an instance
object obj = "Hello, World!";
Type objType = obj.GetType();
Console.WriteLine(objType.FullName);
// Option 3: Using Assembly to get a type by name
Type typeFromAssembly = Type.GetType("System.Int32");
Console.WriteLine(typeFromAssembly.FullName);
}
}
Code language: C# (cs)
Getting Type Information
Once you have obtained a Type
object, you can inspect it to discover the metadata associated with the type. This includes the class name, namespace, base type, and a list of members. Let’s explore some common reflection methods for accessing type information.
using System;
using System.Reflection;
class SampleClass
{
public int MyProperty { get; set; }
public void MyMethod() { }
}
class Program
{
static void Main()
{
Type type = typeof(SampleClass);
// Get the name of the type
Console.WriteLine("Type Name: " + type.Name);
// Get the namespace
Console.WriteLine("Namespace: " + type.Namespace);
// Get the base type
Console.WriteLine("Base Type: " + type.BaseType);
// Get the properties
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine("Property: " + prop.Name);
}
// Get the methods
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
Console.WriteLine("Method: " + method.Name);
}
// Get the fields
FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields)
{
Console.WriteLine("Field: " + field.Name);
}
}
}
Code language: C# (cs)
In this example, we use the Type
object to get information about the class SampleClass
. We can retrieve the name, namespace, base type, properties, methods, and fields of the class using reflection.
Accessing Fields and Properties
Reflection allows you to access fields and properties, including private ones, dynamically at runtime. To access a property or field, you can use the PropertyInfo
or FieldInfo
classes respectively. These classes provide methods to get or set the value of the property or field.
For example:
using System;
using System.Reflection;
class Person
{
public string Name { get; set; }
private int age;
public Person(string name, int age)
{
Name = name;
this.age = age;
}
}
class Program
{
static void Main()
{
Person person = new Person("Alice", 30);
Type type = person.GetType();
// Access public property
PropertyInfo property = type.GetProperty("Name");
Console.WriteLine("Property Name: " + property.GetValue(person));
// Access private field
FieldInfo field = type.GetField("age", BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine("Field Age: " + field.GetValue(person));
// Set new values using reflection
property.SetValue(person, "Bob");
field.SetValue(person, 40);
Console.WriteLine("Updated Name: " + person.Name);
Console.WriteLine("Updated Age (via reflection): " + field.GetValue(person));
}
}
Code language: C# (cs)
In the above example, we use reflection to inspect and manipulate both public and private members of the Person
class. Notice how the BindingFlags.NonPublic
and BindingFlags.Instance
flags are used to access the private field age
. Reflection provides access to private members, which can be useful for testing or debugging, though this should be used cautiously.
Invoking Methods Dynamically
One of the most powerful features of reflection is the ability to invoke methods dynamically at runtime. This is especially useful when working with types that are not known until runtime, such as in plugin systems or serialization frameworks.
You can invoke a method using the MethodInfo
class and its Invoke
method. Here’s an example:
using System;
using System.Reflection;
class Calculator
{
public int Add(int x, int y)
{
return x + y;
}
private int Subtract(int x, int y)
{
return x - y;
}
}
class Program
{
static void Main()
{
Calculator calculator = new Calculator();
Type type = typeof(Calculator);
// Invoke public method
MethodInfo addMethod = type.GetMethod("Add");
object result = addMethod.Invoke(calculator, new object[] { 5, 10 });
Console.WriteLine("Result of Add: " + result);
// Invoke private method
MethodInfo subtractMethod = type.GetMethod("Subtract", BindingFlags.NonPublic | BindingFlags.Instance);
object subtractResult = subtractMethod.Invoke(calculator, new object[] { 10, 5 });
Console.WriteLine("Result of Subtract: " + subtractResult);
}
}
Code language: C# (cs)
In this example, we dynamically invoke the Add
and Subtract
methods on the Calculator
class. Note that Subtract
is a private method, and we use BindingFlags.NonPublic
to access it. By using `
MethodInfo.Invoke()`, you can invoke methods dynamically without needing to know the exact method signatures at compile time.
Working with Constructors
Just like methods, constructors can also be invoked dynamically using reflection. The ConstructorInfo
class provides access to constructors, and you can use its Invoke()
method to create new instances of a type.
Here’s an example:
using System;
using System.Reflection;
class Person
{
public string Name { get; }
public int Age { get; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
class Program
{
static void Main()
{
Type type = typeof(Person);
// Get the constructor
ConstructorInfo constructor = type.GetConstructor(new Type[] { typeof(string), typeof(int) });
// Create an instance dynamically
object personInstance = constructor.Invoke(new object[] { "John", 25 });
// Display the instance information
PropertyInfo nameProperty = type.GetProperty("Name");
PropertyInfo ageProperty = type.GetProperty("Age");
Console.WriteLine("Name: " + nameProperty.GetValue(personInstance));
Console.WriteLine("Age: " + ageProperty.GetValue(personInstance));
}
}
Code language: C# (cs)
In this example, we use reflection to create an instance of the Person
class by dynamically invoking its constructor. This is useful when you don’t know the exact class or its constructor parameters at compile time but need to instantiate the class at runtime.
Exploring Attributes with Reflection
Attributes in C# are a way to add metadata to your classes, methods, properties, and other elements of your code. Using reflection, you can inspect these attributes at runtime. This is particularly useful when working with custom attributes.
Consider this example:
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
class CustomAttribute : Attribute
{
public string Description { get; }
public CustomAttribute(string description)
{
Description = description;
}
}
[Custom("This is a sample class")]
class SampleClass
{
[Custom("This is a sample method")]
public void SampleMethod() { }
}
class Program
{
static void Main()
{
Type type = typeof(SampleClass);
// Check if the class has the custom attribute
if (Attribute.IsDefined(type, typeof(CustomAttribute)))
{
CustomAttribute attr = (CustomAttribute)Attribute.GetCustomAttribute(type, typeof(CustomAttribute));
Console.WriteLine("Class Attribute: " + attr.Description);
}
// Check if the method has the custom attribute
MethodInfo method = type.GetMethod("SampleMethod");
if (Attribute.IsDefined(method, typeof(CustomAttribute)))
{
CustomAttribute attr = (CustomAttribute)Attribute.GetCustomAttribute(method, typeof(CustomAttribute));
Console.WriteLine("Method Attribute: " + attr.Description);
}
}
}
Code language: C# (cs)
In this example, we define a custom attribute CustomAttribute
and apply it to a class and a method. Using reflection, we can inspect whether the class and method have the attribute applied and retrieve its description at runtime.
Dynamic Assembly Loading
In advanced scenarios, you might need to load assemblies dynamically at runtime, especially in plugin architectures or systems that support extensions. The System.Reflection.Assembly
class allows you to load an assembly by its name or path and inspect its types and members.
Here’s how to dynamically load an assembly and inspect its types:
using System;
using System.Reflection;
class Program
{
static void Main()
{
// Load an assembly by its name
Assembly assembly = Assembly.Load("mscorlib");
// Get types in the assembly
Type[] types = assembly.GetTypes();
foreach (var type in types)
{
Console.WriteLine("Type: " + type.FullName);
}
}
}
Code language: C# (cs)
In this example, we load the mscorlib
assembly (which contains core .NET classes like System.String
, System.Int32
, etc.) and list all the types defined within it. This is useful when you need to explore types from external assemblies at runtime.
Conclusion
Reflection is a powerful tool in C# that allows you to inspect and manipulate types, properties, methods, fields, and attributes at runtime. Whether you’re working with dynamic object creation, building extensible frameworks, or inspecting metadata for testing and debugging, reflection opens up new possibilities in your application.
That said, reflection comes with performance costs and can expose private members, which might undermine encapsulation and security. Therefore, it’s best used judiciously and in scenarios where the flexibility it provides is truly necessary. For everyday programming, relying on static type information and traditional object-oriented principles is usually more efficient and safer.
In this tutorial, we covered the basics of reflection, including how to inspect types, access members, invoke methods, create instances, and work with attributes. By applying these concepts, you can leverage the full power of reflection in your C# applications.