C# - Reflection
C# - Reflection
Section titled “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.
Modern Applications of Reflection
Section titled “Modern Applications of Reflection”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.JsonorSystem.Text.Jsonuse 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.
Getting Type Information
Section titled “Getting Type Information”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}");Inspecting Type Members
Section titled “Inspecting Type Members”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).
Example: Listing Members of a Class
Section titled “Example: Listing Members of a Class”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:- _passwordHashDynamically Invoking Members
Section titled “Dynamically Invoking Members”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!Attributes and Reflection
Section titled “Attributes and 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.
Example: Reading a Custom Attribute
Section titled “Example: Reading a Custom Attribute”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 attributepublic class MyTests{ [TestDescription("This test validates the login functionality.")] public void Test_UserLogin_Succeeds() { /* ... */ }}
// 3. Read the attribute using reflectionpublic 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 and Modern Alternatives
Section titled “Performance and Modern Alternatives”- 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.
dynamicKeyword: For COM interop or working with dynamic languages, thedynamickeyword can be a simpler alternative to reflection, though it also has a performance overhead.