C#中能否通过类树路径字符串程序化访问类实例属性值?
在C#中通过类树路径字符串访问嵌套属性值
当然可以实现!在C#里,我们可以利用反射(或者更高效的表达式树)来解析这类斜杠分隔的路径字符串,逐层访问嵌套的属性值。下面结合你给出的类结构,一步步实现这个需求。
1. 基础实现:用反射逐层访问
首先,我们可以写一个通用方法,接收目标对象和路径字符串,拆分路径后通过反射依次获取每个层级的属性值:
示例代码
using System; using System.Reflection; public class LogGeometricModel { public double SmallEndDiameter { get; set; } public double LargeEndDiameter { get; set; } } public class Log { public Guid Id { get; set; } public LogGeometricModel GeometricModel { get; set; } } public class Solution { public DateTime TimeStamp { get; set; } public double Price { get; set; } public Log RotatedLog { get; set; } } public static class PropertyPathAccessor { public static object GetPropertyValue(object target, string path) { if (target == null) throw new ArgumentNullException(nameof(target)); if (string.IsNullOrEmpty(path)) throw new ArgumentException("Path cannot be null or empty.", nameof(path)); var current = target; var pathSegments = path.Split('/'); // 跳过路径开头的根类名(比如示例里的"Solution"),因为我们已经持有该类型的实例 if (pathSegments[0] == current.GetType().Name) { pathSegments = pathSegments[1..]; } foreach (var segment in pathSegments) { if (current == null) throw new InvalidOperationException($"Cannot access property '{segment}' on a null reference."); var property = current.GetType().GetProperty(segment, BindingFlags.Public | BindingFlags.Instance); if (property == null) throw new MissingMemberException(current.GetType().Name, segment); current = property.GetValue(current); } return current; } } // 使用示例 class Program { static void Main() { // 构造测试数据 var testSolution = new Solution { Price = 199.99, TimeStamp = DateTime.Now, RotatedLog = new Log { Id = Guid.NewGuid(), GeometricModel = new LogGeometricModel { SmallEndDiameter = 20.5, LargeEndDiameter = 35.2 } } }; // 访问不同路径的属性值 var smallEnd = (double)PropertyPathAccessor.GetPropertyValue(testSolution, "Solution/RotatedLog/GeometricModel/SmallEndDiameter"); var price = (double)PropertyPathAccessor.GetPropertyValue(testSolution, "Solution/Price"); var logId = (Guid)PropertyPathAccessor.GetPropertyValue(testSolution, "Solution/RotatedLog/Id"); Console.WriteLine($"SmallEndDiameter: {smallEnd}"); Console.WriteLine($"Price: {price}"); Console.WriteLine($"Log Id: {logId}"); } }
关键点说明
- 路径拆分:用
Split('/')把路径拆成单个属性名片段 - 根类名处理:如果路径开头是实例的类型名,直接跳过第一个片段,因为我们已经持有该类型的实例
- 空值防护:在每一步检查当前对象是否为
null,避免空引用异常 - 反射获取属性:用
GetType().GetProperty()获取公共实例属性,再调用GetValue()拿到属性值
2. 性能优化:用表达式树缓存访问器
如果需要频繁访问相同路径,反射的性能会比较差。这时可以用表达式树编译出属性访问的委托并缓存,后续调用直接使用委托,性能接近直接代码访问。
优化后的代码
using System; using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; public static class CachedPropertyPathAccessor { // 缓存:Key是(目标类型 + 路径),Value是编译后的访问委托 private static readonly ConcurrentDictionary<(Type, string), Func<object, object>> _accessorCache = new(); public static object GetPropertyValue(object target, string path) { if (target == null) throw new ArgumentNullException(nameof(target)); if (string.IsNullOrEmpty(path)) throw new ArgumentException("Path cannot be null or empty.", nameof(path)); var targetType = target.GetType(); var cacheKey = (targetType, path); // 从缓存获取或编译新的访问委托 var accessor = _accessorCache.GetOrAdd(cacheKey, key => { var (type, pathStr) = key; var pathSegments = pathStr.Split('/'); // 跳过根类名 if (pathSegments[0] == type.Name) { pathSegments = pathSegments[1..]; } // 构建表达式:参数是object类型的target var parameter = Expression.Parameter(typeof(object), "target"); var current = Expression.Convert(parameter, type); foreach (var segment in pathSegments) { var property = type.GetProperty(segment, BindingFlags.Public | BindingFlags.Instance); if (property == null) throw new MissingMemberException(type.Name, segment); current = Expression.Property(current, property); type = property.PropertyType; } // 把最终结果转为object var body = Expression.Convert(current, typeof(object)); var lambda = Expression.Lambda<Func<object, object>>(body, parameter); return lambda.Compile(); }); return accessor(target); } } // 使用方式和基础版一致,只需替换为CachedPropertyPathAccessor类
优势
- 第一次访问路径时编译表达式,后续直接调用缓存的委托,性能远高于反射
- 适合需要多次访问相同路径的高频场景
3. 容错处理:返回默认值而非抛出异常
如果希望在中间属性为null或者属性不存在时返回默认值,而不是抛出异常,可以修改方法逻辑:
public static object GetPropertyValueOrDefault(object target, string path) { if (target == null || string.IsNullOrEmpty(path)) return default; var current = target; var pathSegments = path.Split('/'); if (pathSegments[0] == current.GetType().Name) { pathSegments = pathSegments[1..]; } foreach (var segment in pathSegments) { if (current == null) return default; var property = current.GetType().GetProperty(segment, BindingFlags.Public | BindingFlags.Instance); if (property == null) return default; current = property.GetValue(current); } return current; }
这样即使路径无效或者中间有null,也会返回对应类型的默认值,避免程序崩溃。
内容的提问来源于stack exchange,提问作者Mireille Jacques
相关产品推荐
相关产品推荐

