如何解决记录 'Lenses' - with 表达式的表达式树
有没有办法为新的 with
运算符构建表达式树?
我正在尝试为只需要选择器并自动生成修改器的记录实现“镜头”功能
我的目标是从“选择器”转换:
Expression<Func<T,TMember>> expression
(即employee => employee.Name
)
致“变异者”:
(employee,newName) => employee with { Name = newName }
对于上面的简单案例,我确实设法做到了这一点,请参阅下面的答案,但这不适用于嵌套案例,即:
record Employee(string Name,int Age);
record Manager(String Name,Employee Employee);
这里我想把ie改成
manager => manager.Employee.Name
到
(manager,newEmployeeName) => manager with { Employee = manager.Employee with { Name = newEmployeeName}}
有什么帮助吗?
解决方法
在@JL0PD 的带领下,我最终转换了:
t => t.Member
(即employee => employee.Name
)
进入:
(t,v) => {
var c = t.<Clone>$();
c.Member = v;
return c;
}
即:
(employee,newName) => {
var c = employee.<Clone>$();
c.Name=newName;
return c;
}
下面是一个记录镜头的完整实现,包括代理缓存
请注意,这不包括嵌套的修改器,所以我上面的问题仍然存在
static class RecLens<T,TMember> {
public static (Func<T,TMember> Selector,Func<T,TMember,T> Mutator) Get(Expression<Func<T,TMember>> expression) {
if (!IsExpressionValid(expression.Body)) throw new Exception($"Lens Invalid expression ({expression})");
// create unique cache key,calc same key for x=>x.p and y=>y.p
var exprStr = expression.Body.ToString();
var dotPos = exprStr.IndexOf(Type.Delimiter);
var cacheKey = typeof(T).FullName + '|' + (dotPos > 0 ? exprStr.Remove(0,exprStr.IndexOf(Type.Delimiter) + 1) : "root");
if (!Cache.TryGetValue(cacheKey,out var res)) {
res = (expression.Compile(),CalcMutator(expression));
Cache = Cache.Add(cacheKey,res);
}
return res;
}
// key: "{srcType.FullName}|{member}",ie: "Test.Organization|DevelopmentDepartment.Manager"
static ImmutableDictionary<string,(Func<T,TMember>,T>)> Cache = ImmutableDictionary<string,T>)>.Empty;
// create delegate: (t,v) => { var c=t.<Clone>$(); c.Member = v; return c; }
static Func<T,T> CalcMutator(Expression<Func<T,TMember>> expression) {
var result = Expression.Variable(typeof(T),"c");
var typeParam = Expression.Parameter(typeof(T),"t");
var valueParam = Expression.Parameter(typeof(TMember),"v");
var cloneMethod = typeof(T).GetMethod("<Clone>$");
if (cloneMethod is null) throw new Exception($"CalcMutatorNo Clone method on {typeof(T)}");
var cloneCall = Expression.Call(typeParam,cloneMethod);
var assignResult = Expression.Assign(result,cloneCall);
var memberInfo = (expression.Body as MemberExpression)!.Member;
var resultMemberAccess = Expression.MakeMemberAccess(result,memberInfo);
var assign = Expression.Assign(resultMemberAccess,valueParam);
var block = Expression.Block(new[] { result },assignResult,assign,result);
var assignLambda = (Expression<Func<T,T>>)Expression.Lambda(block,typeParam,valueParam);
return assignLambda.Compile();
}
// verify that expr is a member expression of its parameter
static bool IsExpressionValid(Expression expr,bool first = true) {
if (expr is ParameterExpression) return !first;
if (expr is MemberExpression memberExpr && memberExpr.Expression is object) return IsExpressionValid(memberExpr.Expression,false);
return false;
}
}
使用:
record Employee(string Name,int Age);
var (Selector,Mutator) = RecLens<Employee,string>.Get(e => e.Name);
var dave = new Employee("Dave",30);
var name = Selector(dave); // "Dave"
var john = Mutator(dave,"John"); // Employee("John",30)
,
CalcMutator
可以处理嵌套属性的方法看起来像这样
static Func<T,TMember>> expression)
{
var typeParam = expression.Parameters.First();
var valueParam = Expression.Parameter(typeof(TMember),"v");
var variables = new List<ParameterExpression>();
var blockExpressions = new List<Expression>();
var property = (MemberExpression)expression.Body;
Expression currentValue = valueParam;
var index = 0;
while (property != null)
{
var variable = Expression.Variable(property.Expression.Type,$"v_{index}");
variables.Add(variable);
var cloneMethod = property.Expression.Type.GetMethod("<Clone>$");
if (cloneMethod is null) throw new Exception($"CalcMutatorNo Clone method on {typeof(T)}");
var cloneCall = Expression.Call(property.Expression,cloneMethod);
var assignClonedToVariable = Expression.Assign(variable,cloneCall);
var accessVariableProperty = Expression.MakeMemberAccess(variable,property.Member);
var assignVariablePropertyValue = Expression.Assign(accessVariableProperty,currentValue);
blockExpressions.Add(assignClonedToVariable);
blockExpressions.Add(assignVariablePropertyValue);
property = property.Expression as MemberExpression;
currentValue = variable;
index++;
}
// Return root object
blockExpressions.Add(currentValue);
var block = Expression.Block(variables,blockExpressions);
var assignLambda = (Expression<Func<T,valueParam);
return assignLambda.Compile();
}
请记住,使用 Cache
实现的 ImmutableDictionary
不是线程安全的。如果你想确保缓存的表达式可以在多线程环境中安全使用,最好使用 ConcurrentDictionary
作为缓存,或者在 ImmutableDictionary
周围应用一些同步原语。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。