c# – 从lambda创建表达式树时如何’取消引用’?
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了c# – 从lambda创建表达式树时如何’取消引用’?,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含5133字,纯文字阅读大概需要8分钟。
内容图文
![c# – 从lambda创建表达式树时如何’取消引用’?](/upload/InfoBanner/zyjiaocheng/767/72731db433d94427bc52296b17e26578.jpg)
假设我有一些返回Expression的函数c:
Func<int, Expression<Func<int>>> c = (int a) => () => a + 3;
现在我想创建另一个Expression,但在创建它时我想调用函数c并将其结果作为新表达式的一部分嵌入:
Expression<Func<int>> d = () => 2 + c(3);
我不能这样做,因为它会将c(3)解释为函数调用转换为表达式,我将得到错误,我无法添加int和Expression< Func< int>>
我希望得到一个值:
(Expression<Func<int>>)( () => 2 + 3 + 3 )
我也有兴趣让这个更复杂的表达式,而不仅仅是这个玩具的例子.
你会怎么用C#做的?
或者,您如何使用我可以在我的C#项目中使用的任何其他CLR语言尽可能少的麻烦?
更复杂的例子:
Func<int, Expression<Func<int>>> c = (int a) => () => a*(a + 3);
Expression<Func<int, int>> d = (x) => 2 + c(3 + x);
即使它出现在两个地方的c体中,也应该在结果表达式中仅评估3 x.
我强烈感觉它无法在C#中实现,因为将lambda赋值给Expression是由编译器完成的,并且是编译时const表达式文字的一种.它类似于使编译器理解普通字符串文字“test”理解模板字符串文字“test ${a b} other”,而C#编译器还没有处于这个开发阶段.
所以我的主要问题实际上是:
什么CLR语言支持语法,可以方便地构建嵌入由其他函数构造的部分的表达式树?
其他可能性是一些库,它可以帮助我使用某种运行时编译模板以这种方式构建表达式树,但我猜这种方式我的表达式代码松开了代码.
似乎F#能够“引用”和“取消引用”(拼接)代码:
https://docs.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/code-quotations
解决方法:
对于这两个示例,实际上可以使用两个表达式访问者来完成(代码已注释):
static class Extensions {
public static TResult FakeInvoke<TResult>(this Delegate instance, params object[] parameters)
{
// this is not intended to be called directly
throw new NotImplementedException();
}
public static TExpression Unwrap<TExpression>(this TExpression exp) where TExpression : Expression {
return (TExpression) new FakeInvokeVisitor().Visit(exp);
}
class FakeInvokeVisitor : ExpressionVisitor {
protected override Expression VisitMethodCall(MethodCallExpression node) {
// replace FakeInvoke call
if (node.Method.Name == "FakeInvoke") {
// first obtain reference to method being called (so, for c.FakeInvoke(...) that will be "c")
var func = (Delegate)Expression.Lambda(node.Arguments[0]).Compile().DynamicInvoke();
// explore method argument names and types
var argumentNames = new List<string>();
var dummyArguments = new List<object>();
foreach (var arg in func.Method.GetParameters()) {
argumentNames.Add(arg.Name);
// create default value for each argument
dummyArguments.Add(arg.ParameterType.IsValueType ? Activator.CreateInstance(arg.ParameterType) : null);
}
// now, invoke function with default arguments to obtain expression (for example, this one () => a*(a + 3)).
// all arguments will have default value (0 in this case), but they are not literal "0" but a reference to "a" member with value 0
var exp = (Expression) func.DynamicInvoke(dummyArguments.ToArray());
// this is expressions representing what we passed to FakeInvoke (for example expression (x + 3))
var argumentExpressions = (NewArrayExpression)node.Arguments[1];
// now invoke second visitor
exp = new InnerFakeInvokeVisitor(argumentExpressions, argumentNames.ToArray()).Visit(exp);
return ((LambdaExpression)exp).Body;
}
return base.VisitMethodCall(node);
}
}
class InnerFakeInvokeVisitor : ExpressionVisitor {
private readonly NewArrayExpression _args;
private readonly string[] _argumentNames;
public InnerFakeInvokeVisitor(NewArrayExpression args, string[] argumentNames) {
_args = args;
_argumentNames = argumentNames;
}
protected override Expression VisitMember(MemberExpression node) {
// if that is a reference to one of our arguments (for example, reference to "a")
if (_argumentNames.Contains(node.Member.Name)) {
// find related expression
var idx = Array.IndexOf(_argumentNames, node.Member.Name);
var argument = _args.Expressions[idx];
var unary = argument as UnaryExpression;
// and replace it. So "a" is replaced with expression "x + 3"
return unary?.Operand ?? argument;
}
return base.VisitMember(node);
}
}
}
可以像这样使用:
Func<int, Expression<Func<int>>> c = (int a) => () => a * (a + 3);
Expression<Func<int, int>> d = (x) => 2 + c.FakeInvoke<int>(3 + x);
d = d.Unwrap(); // this is now "x => (2 + ((3 + x) * ((3 + x) + 3)))"
简单案例:
Func<int, Expression<Func<int>>> c = (int a) => () => a + 3;
Expression<Func<int>> d = () => 2 + c.FakeInvoke<int>(3);
d = d.Unwrap(); // this is now "() => 2 + (3 + 3)
有多个参数:
Func<int, int, Expression<Func<int>>> c = (int a, int b) => () => a * (a + 3) + b;
Expression<Func<int, int>> d = (x) => 2 + c.FakeInvoke<int>(3 + x, x + 5);
d = d.Unwrap(); // "x => (2 + (((3 + x) * ((3 + x) + 3)) + (x + 5)))"
请注意,FakeInvoke不是类型安全的(您应该显式设置返回类型和参数,而不是检查).但这仅仅是例如,在实际使用中你可以创建很多FakeInvoke的重载,如下所示:
public static TResult FakeInvoke<TArg, TResult>(this Func<TArg, Expression<Func<TResult>>> instance, TArg argument) {
// this is not intended to be called directly
throw new NotImplementedException();
}
上面的代码应该被修改一下以正确处理这样的调用(因为参数现在不在单个NewArrayExpression中),但这很容易做到.有了这样的重载,你可以这样做:
Expression<Func<int, int>> d = (x) => 2 + c.FakeInvoke(3 + x); // this is type-safe now, you cannot pass non-integer as "3+x", nor you can pass more or less arguments than required.
内容总结
以上是互联网集市为您收集整理的c# – 从lambda创建表达式树时如何’取消引用’?全部内容,希望文章能够帮你解决c# – 从lambda创建表达式树时如何’取消引用’?所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。