Skip to content

C# - Reflection

Reflection provides a powerful mechanism for inspecting the metadata of types, objects, and assemblies at runtime. Using the classes in the System.Reflection namespace, you can dynamically discover information about loaded assemblies, instantiate types, and invoke their members (methods, properties, fields).

Warning: Reflection is a powerful but advanced feature. It is slower than direct, compile-time-checked code and can make code harder to understand and maintain. Use it judiciously.

While you may not use reflection daily in application code, it’s the engine behind many modern frameworks and tools:

  • Serialization Libraries: Libraries like Newtonsoft.Json or System.Text.Json use reflection to discover public properties on an object to convert it to and from JSON.
  • Dependency Injection (DI) Containers: DI frameworks scan assemblies to find services and their dependencies, then use reflection to construct and inject objects at runtime.
  • Object-Relational Mappers (ORMs): Tools like Entity Framework Core use reflection to map class properties to database table columns.
  • Unit Testing Frameworks: Test runners use reflection to find methods marked with attributes like [TestMethod] or [Fact] to execute them as tests.
  • Plugin Architectures: Applications can load external assemblies (DLLs) and use reflection to find and instantiate types that implement a specific plugin interface.

The entry point to reflection is the System.Type object. You can obtain it in several ways:

using System;
using System.Reflection;
// 1. Using the typeof operator (at compile time)
Type stringType = typeof(string);
// 2. Using the GetType() method on an instance (at runtime)
var greeting = "Hello, World!";
Type instanceType = greeting.GetType();
// 3. Using the Type.GetType() static method (by string name)
// Note: Requires the fully qualified name for types outside the current assembly.
Type listType = Type.GetType("System.Collections.Generic.List`1");
Console.WriteLine($"Type Name: {stringType.Name}");
Console.WriteLine($"Is Public: {stringType.IsPublic}");
Console.WriteLine($"Full Name: {stringType.FullName}");

Once you have a Type object, you can explore its constructors, methods, properties, and fields using methods like GetMethods(), GetProperties(), etc. You can use BindingFlags to filter the results (e.g., to include private or static members).

using System;
using System.Reflection;
using System.Linq;
public class User
{
public int Id { get; set; }
public string Name { get; set; }
private string _passwordHash;
public User(string name) { Name = name; }
public void Greet() => Console.WriteLine($"Hello, {Name}!");
private bool IsPasswordValid(string pass) => true;
}
public class Program
{
public static void Main()
{
Type userType = typeof(User);
Console.WriteLine($"--- Analysis of Type: {userType.Name} ---");
// Get public properties
var properties = userType.GetProperties();
Console.WriteLine("\nPublic Properties:");
foreach (var prop in properties)
{
Console.WriteLine($"- {prop.Name} (Type: {prop.PropertyType.Name})");
}
// Get public methods declared in this type only
var methods = userType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => !m.IsSpecialName); // Exclude property get/set methods
Console.WriteLine("\nPublic Methods:");
foreach (var method in methods)
{
Console.WriteLine($"- {method.Name}");
}
// Get private fields
var privateFields = userType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine("\nPrivate Fields:");
foreach (var field in privateFields)
{
Console.WriteLine($"- {field.Name}");
}
}
}

When executed, this program outputs:

--- Analysis of Type: User ---
Public Properties:
- Id (Type: Int32)
- Name (Type: String)
Public Methods:
- Greet
Private Fields:
- _passwordHash

Reflection also allows you to create instances of types and invoke their methods dynamically.

Example: Creating an Instance and Calling a Method

Section titled “Example: Creating an Instance and Calling a Method”
using System;
using System.Reflection;
public class Greeter
{
public void SayHello(string name) => Console.WriteLine($"Hello, {name} from reflection!");
}
public class Program
{
public static void Main()
{
Type greeterType = typeof(Greeter);
// Create an instance of the Greeter class
object greeterInstance = Activator.CreateInstance(greeterType);
// Get the MethodInfo for the 'SayHello' method
MethodInfo sayHelloMethod = greeterType.GetMethod("SayHello");
// Invoke the method on the instance with parameters
if (sayHelloMethod != null)
{
sayHelloMethod.Invoke(greeterInstance, new object[] { "Alice" });
}
else
{
Console.WriteLine("Method 'SayHello' not found.");
}
}
}

When executed, this program outputs:

Hello, Alice from reflection!

A common use of reflection is to inspect custom attributes. This allows you to add declarative metadata to your code, which can then be queried at runtime to alter behavior.

using System;
using System.Reflection;
// 1. Define a custom attribute
[AttributeUsage(AttributeTargets.Method)]
public class TestDescriptionAttribute : Attribute
{
public string Description { get; }
public TestDescriptionAttribute(string description) => Description = description;
}
// 2. Apply the attribute
public class MyTests
{
[TestDescription("This test validates the login functionality.")]
public void Test_UserLogin_Succeeds() { /* ... */ }
}
// 3. Read the attribute using reflection
public class Program
{
public static void Main()
{
var methodInfo = typeof(MyTests).GetMethod("Test_UserLogin_Succeeds");
var attribute = methodInfo.GetCustomAttribute<TestDescriptionAttribute>();
if (attribute != null)
{
Console.WriteLine($"Test Description: {attribute.Description}");
}
}
}

Output:

Test Description: This test validates the login functionality.
  • Performance Cost: Reflection involves string-based lookups and bypasses compile-time checks, making it significantly slower than direct code. Avoid using it in performance-critical loops.
  • Source Generators: For many common reflection scenarios (like serialization or DI), modern .NET is moving towards Source Generators. These are compile-time tools that generate fast, explicit C# code, giving you the benefits of dynamic behavior without the runtime performance cost of reflection.
  • Generics and Interfaces: Before reaching for reflection, consider if your problem can be solved more cleanly and safely with generics, interfaces, or abstract base classes.
  • dynamic Keyword: For COM interop or working with dynamic languages, the dynamic keyword can be a simpler alternative to reflection, though it also has a performance overhead.