Keywords: C# Reflection | Property Access | Dynamic Programming | PropertyInfo | Type Safety
Abstract: This paper comprehensively examines the implementation of dynamic property value retrieval using string-based reflection in C# programming. Through detailed analysis of the PropertyInfo.GetValue method's core principles, combined with practical scenarios including type safety validation and exception handling, it provides complete solutions and code examples. The discussion extends to performance optimization, edge case management, and best practices across various application contexts, offering technical guidance for developers in dynamic data access, serialization, and data binding scenarios.
Fundamental Concepts of Reflection Mechanism
Reflection is a powerful mechanism provided by the .NET framework that enables programs to inspect type information, create object instances, invoke methods, and access properties at runtime. In C#, the System.Reflection namespace offers comprehensive reflection support, with the PropertyInfo class serving as the core type for property operations.
Core Implementation of Dynamic Property Value Retrieval
Based on the optimal solution from the Q&A data, we can construct a fundamental property value retrieval function:
public static object GetPropValue(object src, string propName)
{
return src.GetType().GetProperty(propName).GetValue(src, null);
}
This concise implementation demonstrates the core workflow of the reflection mechanism: first obtaining type information through the object's GetType method, then using the GetProperty method to locate the corresponding PropertyInfo object based on the property name, and finally calling the GetValue method to retrieve the property value. The null parameter indicates no index parameters are passed, suitable for non-indexed properties.
Type Safety and Exception Handling Enhancements
The generic method proposed in Reference Article 3 provides more comprehensive type safety mechanisms:
public static bool TryGetPropertyValue<TType, TObj>(TObj obj, string propertyName, out TType? value)
{
value = default;
if (obj is null)
{
Console.WriteLine("Object is null.");
return false;
}
PropertyInfo? propertyInfo = typeof(TObj).GetProperty(propertyName);
if (propertyInfo is null)
{
Console.WriteLine($"Property '{propertyName}' not found.");
return false;
}
object? propertyValue = propertyInfo.GetValue(obj);
if (propertyValue is null && Nullable.GetUnderlyingType(typeof(TType)) is not null)
{
return true;
}
if (propertyValue is not TType typedValue)
{
Console.WriteLine($"Property '{propertyName}' is of type {propertyInfo.PropertyType}, got {typeof(TType)}.");
return false;
}
value = typedValue;
return true;
}
This enhanced version ensures type safety through generic constraints and provides complete null value checking and type validation mechanisms. The method returns a boolean value indicating operation success, avoiding direct exception throwing that could disrupt program flow.
In-Depth Analysis of PropertyInfo.GetValue Method
According to the official documentation in Reference Article 2, the PropertyInfo.GetValue method provides three overloaded versions:
// Basic version for non-indexed properties
public object GetValue(object obj);
// Version supporting indexed properties
public virtual object GetValue(object obj, object[] index);
// Complete control version supporting binding flags, binder, index parameters, and culture
public abstract object GetValue(object obj, BindingFlags invokeAttr,
Binder binder, object[] index, CultureInfo culture);
For most application scenarios, the first overloaded version is sufficient. When handling indexed properties like array indexers or collection classes, the second version must be used to pass index parameters.
Reflection Applications in Complex Scenarios
Reference Article 1 demonstrates reflection applications in handling complex nested properties within PowerShell extension data:
string GetPropertyFromExtensionData(PSObject psObject, string propertyName)
{
var extensionData = psObject.Members["ExtensionData"].Value;
var members = extensionData.GetType()
.GetProperty("Members", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(extensionData) as IEnumerable<object>;
var memberType = members.First().GetType();
var nameProperty = memberType.GetProperty("Name",
BindingFlags.Public | BindingFlags.Instance);
var member = members
.Where(x => string.Equals(propertyName,
nameProperty.GetValue(x) as string,
StringComparison.InvariantCultureIgnoreCase))
.FirstOrDefault();
var valueProperty = memberType.GetProperty("Value",
BindingFlags.Public | BindingFlags.Instance);
var value = valueProperty.GetValue(member);
valueProperty = value.GetType().GetProperty("Value",
BindingFlags.Public | BindingFlags.Instance);
return valueProperty.GetValue(value) as string;
}
This example demonstrates how to traverse and access properties within multi-layer nested object structures using reflection, particularly when handling non-public members requires combining BindingFlags parameters.
Performance Optimization and Best Practices
Reflection operations incur significant performance overhead compared to direct property access, requiring careful usage in performance-sensitive scenarios. Below are some optimization recommendations:
// Cache PropertyInfo objects to avoid repeated lookups
private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>>
propertyCache = new();
public static object GetCachedPropertyValue(object src, string propName)
{
var type = src.GetType();
if (!propertyCache.TryGetValue(type, out var typeProperties))
{
typeProperties = new Dictionary<string, PropertyInfo>();
propertyCache[type] = typeProperties;
}
if (!typeProperties.TryGetValue(propName, out var propertyInfo))
{
propertyInfo = type.GetProperty(propName);
typeProperties[propName] = propertyInfo;
}
return propertyInfo?.GetValue(src);
}
By caching PropertyInfo objects, reflection operation overhead can be significantly reduced, especially in loop or frequently called scenarios.
Edge Case Handling
In practical applications, various edge cases must be considered:
public static object GetPropertyValueSafe(object src, string propName)
{
if (src == null)
throw new ArgumentNullException(nameof(src));
if (string.IsNullOrWhiteSpace(propName))
throw new ArgumentException("Property name cannot be null or empty", nameof(propName));
var propertyInfo = src.GetType().GetProperty(propName);
if (propertyInfo == null)
throw new ArgumentException($"Property '{propName}' not found on type {src.GetType().Name}");
if (!propertyInfo.CanRead)
throw new InvalidOperationException($"Property '{propName}' is write-only");
return propertyInfo.GetValue(src);
}
This safe version provides complete parameter validation and error handling, including null object checks, property existence verification, and readability checks.
Practical Application Scenarios
Dynamic property access holds significant value in multiple scenarios:
// Data binding and serialization
public static void SerializeToDictionary<T>(T obj, Dictionary<string, object> dictionary)
{
foreach (var property in typeof(T).GetProperties())
{
if (property.CanRead)
{
dictionary[property.Name] = property.GetValue(obj);
}
}
}
// Dynamic data access
public static T CreateDynamicAccessor<T>() where T : class, new()
{
var instance = new T();
var properties = typeof(T).GetProperties();
// Implement dynamic property access logic
return instance;
}
These examples demonstrate the practical application value of reflection mechanisms in scenarios such as data serialization and dynamic object creation.