You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:51:37