Dynamic Type Inspection with Reflection API in .NET

Understanding Reflection in .NET

Reflection lets you inspect types, methods, and properties at runtime without knowing them at compile time. You'll use it when building frameworks, serializers, dependency injection containers, or plugin systems that work with types dynamically.

The System.Reflection namespace provides classes like Type, MethodInfo, PropertyInfo, and FieldInfo that let you examine and manipulate code metadata. While powerful, reflection comes with performance costs, so you'll want to use it thoughtfully.

You'll learn how to inspect types, invoke methods dynamically, access properties and fields, and apply these techniques to real-world scenarios while avoiding common pitfalls.

Inspecting Types and Members

The Type class is your starting point for reflection. You can get type information using typeof, GetType(), or Type.GetType() with a string name. Once you have a Type object, you can explore its members.

TypeInspection.cs - Basic Type Inspection
using System;
using System.Reflection;

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    private decimal price;

    public void UpdatePrice(decimal newPrice)
    {
        price = newPrice;
    }
}

public class ReflectionExample
{
    public static void InspectType()
    {
        // Get type information
        Type productType = typeof(Product);
        
        Console.WriteLine($"Type Name: {productType.Name}");
        Console.WriteLine($"Full Name: {productType.FullName}");
        Console.WriteLine($"Namespace: {productType.Namespace}");
        
        // Get properties
        PropertyInfo[] properties = productType.GetProperties();
        Console.WriteLine("\nPublic Properties:");
        foreach (var prop in properties)
        {
            Console.WriteLine($"  {prop.Name} ({prop.PropertyType.Name})");
        }
        
        // Get methods
        MethodInfo[] methods = productType.GetMethods(
            BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
        Console.WriteLine("\nPublic Methods:");
        foreach (var method in methods)
        {
            Console.WriteLine($"  {method.Name}");
        }
        
        // Get fields (including private)
        FieldInfo[] fields = productType.GetFields(
            BindingFlags.NonPublic | BindingFlags.Instance);
        Console.WriteLine("\nPrivate Fields:");
        foreach (var field in fields)
        {
            Console.WriteLine($"  {field.Name}");
        }
    }
}

BindingFlags control which members you retrieve. Combine flags like Public, NonPublic, Instance, and Static to filter members. DeclaredOnly excludes inherited members.

Invoking Methods Dynamically

Once you have method information, you can invoke methods on objects without knowing the method names at compile time. This is useful for plugin systems or when executing commands based on user input.

DynamicInvocation.cs - Calling Methods
public class Calculator
{
    public int Add(int a, int b) => a + b;
    public int Multiply(int a, int b) => a * b;
    public double Divide(double a, double b) => a / b;
}

public class DynamicMethodCaller
{
    public static void CallMethodByName(string methodName)
    {
        var calculator = new Calculator();
        Type type = calculator.GetType();
        
        // Get method by name
        MethodInfo method = type.GetMethod(methodName);
        
        if (method == null)
        {
            Console.WriteLine($"Method {methodName} not found");
            return;
        }
        
        // Invoke with parameters
        object[] parameters = { 10, 5 };
        object result = method.Invoke(calculator, parameters);
        
        Console.WriteLine($"{methodName}(10, 5) = {result}");
    }
    
    public static void CallAllMethods()
    {
        var calculator = new Calculator();
        Type type = typeof(Calculator);
        
        // Get all public methods
        MethodInfo[] methods = type.GetMethods(
            BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
        
        foreach (var method in methods)
        {
            // Check parameter count
            ParameterInfo[] parameters = method.GetParameters();
            if (parameters.Length == 2)
            {
                object[] args = { 20, 4 };
                object result = method.Invoke(calculator, args);
                Console.WriteLine($"{method.Name}: {result}");
            }
        }
    }
}

// Usage
DynamicMethodCaller.CallMethodByName("Add");
DynamicMethodCaller.CallAllMethods();

Method invocation requires matching parameter types. If types don't match, you'll get exceptions. Always validate parameters before invoking methods dynamically.

Accessing Properties and Fields

Reflection lets you read and write property values dynamically. This is the foundation of serialization libraries and object mappers that convert between different data formats.

PropertyAccess.cs - Dynamic Property Access
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

public class PropertyHelper
{
    public static void CopyProperties(object source, object destination)
    {
        Type sourceType = source.GetType();
        Type destType = destination.GetType();
        
        PropertyInfo[] sourceProperties = sourceType.GetProperties();
        
        foreach (var sourceProp in sourceProperties)
        {
            // Find matching property in destination
            PropertyInfo destProp = destType.GetProperty(sourceProp.Name);
            
            if (destProp != null && destProp.CanWrite)
            {
                // Get value from source
                object value = sourceProp.GetValue(source);
                
                // Set value in destination
                destProp.SetValue(destination, value);
            }
        }
    }
    
    public static void PrintObjectProperties(object obj)
    {
        Type type = obj.GetType();
        PropertyInfo[] properties = type.GetProperties();
        
        Console.WriteLine($"Properties of {type.Name}:");
        foreach (var prop in properties)
        {
            object value = prop.GetValue(obj);
            Console.WriteLine($"  {prop.Name}: {value}");
        }
    }
}

// Usage
var person1 = new Person { FirstName = "John", LastName = "Doe", Age = 30 };
var person2 = new Person();

PropertyHelper.CopyProperties(person1, person2);
PropertyHelper.PrintObjectProperties(person2);

Working with Attributes

Attributes provide metadata about types and members. Reflection lets you read these attributes at runtime, enabling declarative programming patterns used by frameworks like ASP.NET Core and Entity Framework.

AttributeUsage.cs - Reading Attributes
using System;
using System.ComponentModel.DataAnnotations;
using System.Reflection;

public class User
{
    [Required]
    [StringLength(50)]
    public string Username { get; set; }
    
    [EmailAddress]
    public string Email { get; set; }
    
    [Range(18, 100)]
    public int Age { get; set; }
}

public class AttributeValidator
{
    public static bool ValidateObject(object obj)
    {
        Type type = obj.GetType();
        PropertyInfo[] properties = type.GetProperties();
        bool isValid = true;
        
        foreach (var prop in properties)
        {
            // Get all attributes on this property
            object[] attributes = prop.GetCustomAttributes(true);
            
            foreach (var attr in attributes)
            {
                if (attr is RequiredAttribute)
                {
                    var value = prop.GetValue(obj);
                    if (value == null || string.IsNullOrEmpty(value.ToString()))
                    {
                        Console.WriteLine($"{prop.Name} is required");
                        isValid = false;
                    }
                }
                
                if (attr is StringLengthAttribute stringLength)
                {
                    var value = prop.GetValue(obj)?.ToString();
                    if (value?.Length > stringLength.MaximumLength)
                    {
                        Console.WriteLine(
                            $"{prop.Name} exceeds max length of {stringLength.MaximumLength}");
                        isValid = false;
                    }
                }
            }
        }
        
        return isValid;
    }
}

// Usage
var user = new User { Username = "john_doe", Email = "invalid-email", Age = 25 };
bool isValid = AttributeValidator.ValidateObject(user);

GetCustomAttributes returns all attributes applied to a member. You can filter by attribute type or get all attributes and check their types manually.

Performance and Best Practices

Reflection is powerful but slower than direct code. Cache reflection results whenever possible to minimize performance impact.

ReflectionCache.cs - Caching Strategy
using System.Collections.Concurrent;

public static class ReflectionCache
{
    private static readonly ConcurrentDictionary<Type, PropertyInfo[]> 
        PropertyCache = new();
    
    private static readonly ConcurrentDictionary<string, MethodInfo> 
        MethodCache = new();
    
    public static PropertyInfo[] GetProperties(Type type)
    {
        return PropertyCache.GetOrAdd(type, t => t.GetProperties());
    }
    
    public static MethodInfo GetMethod(Type type, string methodName)
    {
        string key = $"{type.FullName}.{methodName}";
        return MethodCache.GetOrAdd(key, _ => type.GetMethod(methodName));
    }
}

// Usage - much faster on repeated calls
Type type = typeof(Product);
PropertyInfo[] props = ReflectionCache.GetProperties(type);
MethodInfo method = ReflectionCache.GetMethod(type, "UpdatePrice");

Additional tips: Use compiled expressions for repeated invocations. Consider source generators as alternatives when you know types at compile time. Avoid reflection in hot paths where performance matters most.

Frequently Asked Questions (FAQ)

When should I use reflection in my applications?

Use reflection for plugin systems, serialization frameworks, dependency injection containers, and when working with types unknown at compile time. Avoid it for regular application logic due to performance overhead. Consider alternatives like source generators or generic constraints when possible for better performance.

Does reflection impact performance?

Yes, reflection is slower than direct code calls due to runtime type lookups and security checks. Cache reflection results like MethodInfo and PropertyInfo objects. Use compiled expressions or delegates for repeated invocations. Modern .NET includes optimizations, but reflection should still be used judiciously in performance-critical paths.

Can reflection access private members?

Yes, reflection can access private fields, methods, and properties using BindingFlags.NonPublic. However, accessing private members breaks encapsulation and can cause maintenance issues. Use this capability sparingly, typically only in testing frameworks or specialized scenarios where you need deep inspection of objects.

Back to Articles