如何改写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
相关产品推荐
相关产品推荐

