动态参数的动态Lambda表达式

19

鉴于这个类

public class Foo
{
    public string Name { get; set; }
}

这个方法(在其他类中)...

private Func<Foo, string> Compile(string body)
{
    ParameterExpression prm = Expression.Parameter(typeof(Foo), "foo");
    LambdaExpression exp = DynamicExpressionParser.ParseLambda(new[] { prm }, typeof(string), body);
    return (Func<Foo, string>)exp.Compile();
}

会获取 lambda 表达式的右侧,并返回一个委托。因此,如果像这样调用它:

Foo f = new Foo { Name = "Hamilton Academicals" };
//foo => foo.Name.Substring(0,3)
Func<Foo, string> fn = Compile("foo.Name.Substring(0,3)");
string sub = fn(f);

那么sub的值将会是"Ham"。

一切都很好,但我想让Foo成为DynamicObject的子类(这样我就可以实现TryGetMember来动态地计算属性值),所以我想获取表达式的等效内容。

Func<dynamic, dynamic> fn = foo => foo.Name.Substring(0,3);
我尝试使用自定义的CallSiteBinder来使用Expression.Dynamic,但是当我尝试动态访问foo.Bar时出现“在类型Object中不存在属性或字段条款”。我认为这是因为需要动态分派对foo.Bar的调用才能获得,但这对我来说行不通,因为一个关键目标是让用户输入一个简单表达式并执行它。这可行吗?

1
为此,我假设您需要另一个专门处理动态表达式的解析器。这一个不会起作用。也许只需使用完整的编译器(Roslyn)?期望有多复杂的表达式? - Evk
@Evk - 是的。我发帖后得出了同样的结论。表达式将是简单的(一行代码)。不能使用Roslyn,因为生成的程序集不符合垃圾回收的条件。 - user2729292
1
你可以将生成的程序集加载到单独的应用程序域中并卸载它们。当然,这会影响性能,通常不是很好的解决方案,但根据您对性能的要求,可能比没有更好。 - Evk
真实的…除了我想针对.NET Core。 - user2729292
1
那么,我想你就没那么幸运了,至少在.NET Core实现dll卸载之前是这样的(据我所知,这是即将推出的功能)。实现自己的解析器来处理动态表达式可能会相当不容易。 - Evk
老问题,但只是出于兴趣...这些结果的Func<>如何评估?-是否可以用调用某个静态方法获取运行时属性值的方式替换属性访问器?-不过,在将其传递给DynamicExpressionParser之前,您必须改变表达式。 - Sam
1个回答

1
我已经将它工作起来了,如果对您有帮助的话:
编译器:
using System;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;


namespace ConsoleApp4
{
    public class Compiler
    {
        public static Func<CustomDynamic, TResult> Compile<TResult>(string body)
        {
            ParameterExpression prm = Expression.Parameter(typeof(CustomDynamic), typeof(CustomDynamic).Name);
            LambdaExpression exp = DynamicExpressionParser.ParseLambda(new[] { prm }, typeof(TResult), body);
            return (Func<CustomDynamic, TResult>)exp.Compile();
        }
    }
}

动态对象:

using System.Collections.Generic;
using System.Dynamic;

namespace ConsoleApp4
{
    public class CustomDynamic
    {
        ExpandoObject _values;
        public CustomDynamic()
        {
            _values = new ExpandoObject();
        }

        public void AddProperty(string propertyName, object propertyValue)
        {
            var expandoDict = _values as IDictionary<string, object>;
            if (expandoDict.ContainsKey(propertyName))
                expandoDict[propertyName] = propertyValue;
            else
                expandoDict.Add(propertyName, propertyValue);
        }

        public string GetString(string propertyName)
        {
            var expandoDict = _values as IDictionary<string, object>;
            if (expandoDict.ContainsKey(propertyName))
                return (string)expandoDict[propertyName];
            else
                throw new KeyNotFoundException($"dynamic object did not contain property {propertyName}");
        }
        public int GetInt(string propertyName)
        {
            var expandoDict = _values as IDictionary<string, object>;
            if (expandoDict.ContainsKey(propertyName))
                return (int)expandoDict[propertyName];
            else
                throw new KeyNotFoundException($"dynamic object did not contain property {propertyName}");
        }
    }
}

使用案例:
using System;

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            CustomDynamic f = new CustomDynamic();
            f.AddProperty("Name", "Hamiltonian Physics");

            Func<CustomDynamic, string> fn = Compiler.Compile<string>("CustomDynamic.GetString(\"Name\").SubString(0, 3)");

            string sub = fn(f);

            Console.WriteLine(sub);
            Console.ReadLine();
        }
    }
}

它只是利用了ExpandoObject类。不幸的是,你需要通过它的API来创建对象,例如为每个属性添加“AddProperty”,但是嘿哎。当涉及到通用方法时,DynamicExpressionParser.ParseLambda有点麻烦,所以我不得不创建特定类型的访问器,这不是最好的选择,但它正在起作用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接