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

如何改写C#的ToDynamicSelectFields方法以支持Lambda表达式选择属性

当然可行!而且这种方式能彻底解决字符串硬编码带来的拼写问题,还能享受IDE智能提示和编译时检查

你的需求完全可以实现——核心思路是通过解析Lambda表达式的表达式树,提取你指定的属性名,替代原来的字符串列表。这样不仅避免了拼写错误,代码可读性和可维护性也会提升不少。

实现方案

下面是改写后的扩展方法,支持你想要的a => a.i && a.c写法,同时也兼容更规范的匿名类写法(比如a => new {a.i, a.c}):

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq.Expressions;
using System.ComponentModel;

public static class ObjectExtensions
{
    public static dynamic ToDynamicSelectFields<T>(this T obj, Expression<Func<T, object>> selector)
    {
        if (obj == null)
            throw new ArgumentNullException(nameof(obj));
        if (selector == null)
            throw new ArgumentNullException(nameof(selector));

        var expandoDict = (IDictionary<string, object>)new ExpandoObject();
        var expressionBody = selector.Body;

        // 提取所有成员访问节点(处理&&连接或匿名类的情况)
        var memberExpressions = ExtractMemberExpressions(expressionBody);
        foreach (var memberExpr in memberExpressions)
        {
            var propertyName = memberExpr.Member.Name;
            var property = TypeDescriptor.GetProperties(typeof(T))[propertyName];
            expandoDict.Add(propertyName, property.GetValue(obj));
        }

        return expandoDict;
    }

    // 递归遍历表达式树,提取所有成员访问节点
    private static IEnumerable<MemberExpression> ExtractMemberExpressions(Expression expression)
    {
        // 处理&&连接的逻辑表达式
        if (expression is BinaryExpression binaryExpr && binaryExpr.NodeType == ExpressionType.AndAlso)
        {
            foreach (var left in ExtractMemberExpressions(binaryExpr.Left))
                yield return left;
            foreach (var right in ExtractMemberExpressions(binaryExpr.Right))
                yield return right;
        }
        // 处理直接的成员访问(比如a.i)
        else if (expression is MemberExpression memberExpr)
        {
            yield return memberExpr;
        }
        // 处理值类型装箱的情况(比如int转object时的Convert表达式)
        else if (expression is UnaryExpression unaryExpr && unaryExpr.NodeType == ExpressionType.Convert)
        {
            var innerMember = ExtractMemberExpressions(unaryExpr.Operand).FirstOrDefault();
            if (innerMember != null)
                yield return innerMember;
        }
        // 处理匿名类初始化(更规范的写法)
        else if (expression is NewExpression newExpr)
        {
            foreach (var arg in newExpr.Arguments)
            {
                var member = ExtractMemberExpressions(arg).FirstOrDefault();
                if (member != null)
                    yield return member;
            }
        }
    }
}

使用示例

注意:原类A的字段需要改为公共属性(TypeDescriptor默认读取公共属性,如果你要支持字段,可以把TypeDescriptor.GetProperties换成反射的GetFields):

class A 
{ 
    public int i { get; set; } 
    public char c { get; set; } 
    public string s { get; set; } 
}

// 初始化实例
var a = new A { i = 42, c = 'z', s = "hello" };

// 你想要的写法:用&&选择字段
dynamic d1 = a.ToDynamicSelectFields(x => x.i && x.c);
Console.WriteLine(d1.i); // 输出42
Console.WriteLine(d1.c); // 输出z

// 更推荐的规范写法:用匿名类(语义更清晰)
dynamic d2 = a.ToDynamicSelectFields(x => new { x.i, x.c });
// 效果和d1完全一致

为什么这能解决拼写问题?

因为Lambda表达式里的x.i和x.c是强类型引用——如果你拼写错了(比如写成x.ii),IDE会立刻报错,编译也通不过,彻底避免了原来字符串列表的运行时错误。

补充说明

  • 用&&来选择字段其实是利用了表达式树的结构,严格来说语义上有点牵强(毕竟&&是逻辑运算符),所以更推荐用匿名类的写法,代码可读性更好。
  • 如果你的类里用的是字段而不是属性,可以把方法里的TypeDescriptor.GetProperties换成typeof(T).GetFields(),并调整对应的取值逻辑。

内容的提问来源于stack exchange,提问作者Thomas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:34:56