ReflectionExtensions
Provides advanced reflection utilities and type operations.
Overview
The ReflectionExtensions class offers powerful reflection utilities for type introspection, member access, attribute discovery, and dynamic operations.
API Reference
Core Methods
Type Information
GetFriendlyName(Type type)
Gets a human-readable name for a type, including generic types.
var listType = typeof(List<string>);
var friendlyName = listType.GetFriendlyName(); // "List<String>"
var dictionaryType = typeof(Dictionary<int, List<string>>);
var complexName = dictionaryType.GetFriendlyName(); // "Dictionary<Int32, List<String>>"
IsNullable(Type type)
Checks if a type is nullable (reference type or Nullable
bool isNullable1 = typeof(string).IsNullable(); // true (reference type)
bool isNullable2 = typeof(int?).IsNullable(); // true (Nullable<int>)
bool isNullable3 = typeof(int).IsNullable(); // false (value type)
GetUnderlyingType(Type type)
Gets the underlying type for nullable types.
var underlyingType = typeof(int?).GetUnderlyingType(); // typeof(int)
var sameType = typeof(string).GetUnderlyingType(); // typeof(string)
Attribute Operations
HasAttribute<T>(MemberInfo member) where T : Attribute
Checks if a member has a specific attribute.
var property = typeof(User).GetProperty("Email");
bool hasRequired = property.HasAttribute<RequiredAttribute>(); // true/false
var method = typeof(Controller).GetMethod("GetUsers");
bool hasAuth = method.HasAttribute<AuthorizeAttribute>();
GetAttribute<T>(MemberInfo member) where T : Attribute
Gets a specific attribute from a member.
var property = typeof(User).GetProperty("Name");
var maxLength = property.GetAttribute<MaxLengthAttribute>();
if (maxLength != null)
{
Console.WriteLine($"Max length: {maxLength.Length}");
}
GetAttributes<T>(MemberInfo member) where T : Attribute
Gets all attributes of a specific type from a member.
var method = typeof(Controller).GetMethod("UpdateUser");
var validationAttrs = method.GetAttributes<ValidationAttribute>();
foreach (var attr in validationAttrs)
{
Console.WriteLine($"Validation: {attr.GetType().Name}");
}
Member Discovery
GetPropertiesWithAttribute<T>(Type type) where T : Attribute
Gets all properties that have a specific attribute.
var jsonProperties = typeof(ApiModel).GetPropertiesWithAttribute<JsonPropertyNameAttribute>();
foreach (var (property, attribute) in jsonProperties)
{
Console.WriteLine($"Property: {property.Name}, JSON Name: {attribute.Name}");
}
GetMethodsWithAttribute<T>(Type type) where T : Attribute
Gets all methods that have a specific attribute.
var endpoints = typeof(ApiController).GetMethodsWithAttribute<HttpGetAttribute>();
foreach (var (method, attribute) in endpoints)
{
Console.WriteLine($"Endpoint: {method.Name}, Route: {attribute.Template}");
}
Advanced Operations
Dynamic Invocation
InvokeMethod(object instance, string methodName, params object[] args)
Dynamically invokes a method on an object.
var calculator = new Calculator();
var result = calculator.InvokeMethod("Add", 5, 3); // Calls Add(5, 3)
Console.WriteLine($"Result: {result}");
GetPropertyValue(object instance, string propertyName)
Gets the value of a property dynamically.
var user = new User { Name = "John", Email = "john@example.com" };
var name = user.GetPropertyValue("Name"); // "John"
var email = user.GetPropertyValue("Email"); // "john@example.com"
SetPropertyValue(object instance, string propertyName, object value)
Sets the value of a property dynamically.
var user = new User();
user.SetPropertyValue("Name", "Jane");
user.SetPropertyValue("Age", 25);
Type Conversion
CanConvertTo(Type sourceType, Type targetType)
Checks if one type can be converted to another.
bool canConvert1 = typeof(string).CanConvertTo(typeof(int)); // false
bool canConvert2 = typeof(int).CanConvertTo(typeof(double)); // true
bool canConvert3 = typeof(string).CanConvertTo(typeof(DateTime)); // true (if parseable)
ConvertValue(object value, Type targetType)
Converts a value to a target type.
var stringValue = "123";
var intValue = stringValue.ConvertValue(typeof(int)); // 123
var dateString = "2024-01-01";
var dateValue = dateString.ConvertValue(typeof(DateTime)); // DateTime object
Practical Examples
Object Mapper
public class SimpleObjectMapper
{
public TTarget Map<TSource, TTarget>(TSource source) where TTarget : new()
{
var target = new TTarget();
var sourceType = typeof(TSource);
var targetType = typeof(TTarget);
var sourceProperties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var targetProperties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanWrite)
.ToDictionary(p => p.Name, p => p);
foreach (var sourceProp in sourceProperties)
{
if (targetProperties.TryGetValue(sourceProp.Name, out var targetProp))
{
var sourceValue = source.GetPropertyValue(sourceProp.Name);
if (sourceValue != null && targetProp.PropertyType.CanConvertTo(sourceProp.PropertyType))
{
var convertedValue = sourceValue.ConvertValue(targetProp.PropertyType);
target.SetPropertyValue(targetProp.Name, convertedValue);
}
}
}
return target;
}
}
Validation Framework
public class ValidationEngine
{
public ValidationResult Validate(object instance)
{
var type = instance.GetType();
var errors = new List<string>();
// Validate properties with validation attributes
var validatedProperties = type.GetPropertiesWithAttribute<ValidationAttribute>();
foreach (var (property, validationAttr) in validatedProperties)
{
var value = instance.GetPropertyValue(property.Name);
if (!validationAttr.IsValid(value))
{
errors.Add($"{property.Name}: {validationAttr.ErrorMessage}");
}
}
// Check for required properties
var requiredProperties = type.GetPropertiesWithAttribute<RequiredAttribute>();
foreach (var (property, _) in requiredProperties)
{
var value = instance.GetPropertyValue(property.Name);
if (value == null || (value is string str && string.IsNullOrWhiteSpace(str)))
{
errors.Add($"{property.Name} is required");
}
}
return new ValidationResult
{
IsValid = errors.Count == 0,
Errors = errors
};
}
}
public class ValidationResult
{
public bool IsValid { get; set; }
public List<string> Errors { get; set; } = new();
}
Configuration Binding
public class ConfigurationBinder
{
private readonly Dictionary<string, string> _configuration;
public ConfigurationBinder(Dictionary<string, string> configuration)
{
_configuration = configuration;
}
public T Bind<T>() where T : new()
{
var instance = new T();
var type = typeof(T);
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanWrite);
foreach (var property in properties)
{
var configKey = GetConfigurationKey(property);
if (_configuration.TryGetValue(configKey, out var configValue))
{
try
{
var convertedValue = configValue.ConvertValue(property.PropertyType);
instance.SetPropertyValue(property.Name, convertedValue);
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to bind configuration key '{configKey}' to property '{property.Name}'", ex);
}
}
}
return instance;
}
private string GetConfigurationKey(PropertyInfo property)
{
var configAttr = property.GetAttribute<ConfigurationKeyAttribute>();
return configAttr?.Key ?? property.Name;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class ConfigurationKeyAttribute : Attribute
{
public string Key { get; }
public ConfigurationKeyAttribute(string key)
{
Key = key;
}
}
Command Pattern Implementation
public class CommandDispatcher
{
private readonly Dictionary<Type, Func<object, Task>> _handlers = new();
public void RegisterHandler<TCommand>(Func<TCommand, Task> handler)
{
_handlers[typeof(TCommand)] = command => handler((TCommand)command);
}
public async Task DispatchAsync(object command)
{
var commandType = command.GetType();
if (_handlers.TryGetValue(commandType, out var handler))
{
await handler(command);
}
else
{
throw new InvalidOperationException($"No handler registered for command type: {commandType.GetFriendlyName()}");
}
}
public void RegisterHandlersFromAssembly(Assembly assembly)
{
var handlerTypes = assembly.GetTypesWithAttribute<CommandHandlerAttribute>();
foreach (var (handlerType, attribute) in handlerTypes)
{
var handler = Activator.CreateInstance(handlerType.Type);
var methods = handlerType.Type.GetMethodsWithAttribute<HandlesAttribute>();
foreach (var (method, handlesAttr) in methods)
{
var commandType = handlesAttr.CommandType;
var handlerFunc = CreateHandlerFunc(handler, method, commandType);
_handlers[commandType] = handlerFunc;
}
}
}
private Func<object, Task> CreateHandlerFunc(object handler, MethodInfo method, Type commandType)
{
return async command =>
{
var result = handler.InvokeMethod(method.Name, command);
if (result is Task task)
{
await task;
}
};
}
}
[AttributeUsage(AttributeTargets.Class)]
public class CommandHandlerAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Method)]
public class HandlesAttribute : Attribute
{
public Type CommandType { get; }
public HandlesAttribute(Type commandType)
{
CommandType = commandType;
}
}
Performance Considerations
- Reflection operations are slower than direct code access
- Cache reflection results (PropertyInfo, MethodInfo) when possible
- Use compiled expressions for frequently accessed members
- Consider using delegates for dynamic method invocation
Thread Safety
- Type metadata operations are thread-safe
- Property and method invocation should be synchronized if modifying shared state
- Caching reflection results is safe for concurrent read access
Best Practices
- Cache Reflection Results: Store PropertyInfo and MethodInfo objects for reuse
- Error Handling: Handle reflection exceptions gracefully
- Type Safety: Validate types and parameters before dynamic operations
- Performance: Use direct access when possible, reflection when necessary
- Security: Validate member names and types to prevent security issues