使用表达式树实现字符串形式的表达式访问对象属性
使用表达式树实现字符串形式的表达式访问对象属性
在C#中,使用表达式树(Expression Trees)可以动态地构建和编译代码,这使得我们可以通过字符串形式的表达式来访问对象的属性。下面是实现这一功能的几种方法:
1. 基本实现方式
using System;
using System.Linq.Expressions;
using System.Reflection;public static class PropertyAccessor
{
public static Func<T, object> CreatePropertyGetter<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T), "x");
Expression property = Expression.Property(parameter, propertyName);
Expression conversion = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(conversion, parameter).Compile();
}public static Action<T, object> CreatePropertySetter<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T), "x");
var valueParameter = Expression.Parameter(typeof(object), "value");
var property = Expression.Property(parameter, propertyName);
var assignment = Expression.Assign(
property,
Expression.Convert(valueParameter, property.Type));
return Expression.Lambda<Action<T, object>>(assignment, parameter, valueParameter).Compile();
}
}
使用示例
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}// 使用
var person = new Person { Name = "Alice", Age = 30 };// 获取属性
var getName = PropertyAccessor.CreatePropertyGetter<Person>("Name");
Console.WriteLine(getName(person)); // 输出 "Alice"// 设置属性
var setAge = PropertyAccessor.CreatePropertySetter<Person>("Age");
setAge(person, 35);
Console.WriteLine(person.Age); // 输出 35
2. 支持嵌套属性访问
public static Func<T, object> CreateNestedPropertyGetter<T>(string propertyPath)
{
var parameter = Expression.Parameter(typeof(T), "x");
Expression property = parameter;foreach (var member in propertyPath.Split('.'))
{
property = Expression.PropertyOrField(property, member);
}Expression conversion = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(conversion, parameter).Compile();
}
使用示例
public class Address
{
public string City { get; set; }
}public class Person
{
public string Name { get; set; }
public Address HomeAddress { get; set; }
}// 使用
var person = new Person
{
Name = "Bob",
HomeAddress = new Address { City = "New York" }
};var getCity = PropertyAccessor.CreateNestedPropertyGetter<Person>("HomeAddress.City");
Console.WriteLine(getCity(person)); // 输出 "New York"
3. 更完整的表达式解析
对于更复杂的表达式(如数组索引、方法调用等),可以使用 System.Linq.Dynamic.Core 库:
// 安装 NuGet 包: System.Linq.Dynamic.Coreusing System.Linq.Dynamic.Core;public static class DynamicAccessor
{
public static Func<T, object> CreateExpression<T>(string expression)
{
var parameter = Expression.Parameter(typeof(T), "x");
var lambda = DynamicExpressionParser.ParseLambda(
new[] { parameter },
typeof(object),
expression);
return (Func<T, object>)lambda.Compile();
}
}
使用示例
public class Company
{
public List<Person> Employees { get; set; } = new List<Person>();
}// 使用
var company = new Company
{
Employees = new List<Person>
{
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 35 }
}
};var getFirstEmployeeName = DynamicAccessor.CreateExpression<Company>("Employees.Name");
Console.WriteLine(getFirstEmployeeName(company)); // 输出 "Alice"
4. 性能考虑
表达式树在第一次构建和编译时会有性能开销,但编译后的委托执行速度与直接编写的代码几乎相同。如果需要在循环中重复使用,应该缓存编译后的委托。
private static Dictionary<string, Func<T, object>> _getterCache = new Dictionary<string, Func<T, object>>();public static Func<T, object> GetCachedPropertyGetter<T>(string propertyName)
{
if (!_getterCache.TryGetValue(propertyName, out var getter))
{
getter = CreatePropertyGetter<T>(propertyName);
_getterCache[propertyName] = getter;
}
return getter;
}
注意事项
- 属性名称或路径错误会抛出异常,可以添加错误处理
- 对于私有成员,需要设置适当的绑定标志
- 表达式树不支持所有C#语法,复杂逻辑可能需要使用其他方法
以上方法提供了灵活的方式来通过字符串表达式访问对象属性,可以根据具体需求选择适合的实现方式。
