c# – 从lambda创建表达式树时如何’取消引用’?

c# – 从lambda创建表达式树时如何’取消引用’?,第1张

概述假设我有一些返回Expression的函数c: Func<int, Expression<Func<int>>> c = (int a) => () => a + 3; 现在我想创建另一个Expression,但在创建它时我想调用函数c并将其结果作为新表达式的一部分嵌入: Expression<Func<int>> d = () => 2 + c(3); 我不能这样做,因为它会将c(3)解释为函数 假设我有一些返回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创建表达式树时如何’取消引用’?所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/1227125.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-06-05
下一篇2022-06-05

发表评论

登录后才能评论

评论列表(0条)

    保存