【问题标题】:Using expression trees to construct an object with unknown members使用表达式树构造具有未知成员的对象
【发布时间】:2013-12-15 11:11:31
【问题描述】:

我正在尝试创建一个通用函数,该函数将采用 2 个结构实例并使用传入实例的值创建一个新实例。我大部分时间都在那里,但我无法弄清楚如何构建表达式树以将新值作为 MemberInit 中的参数(第一次使用表达式树)。

我尽量避免产生垃圾(所以不要装箱)。

这是我目前所拥有的:

private static readonly Dictionary<Type, FieldInfo[]> fieldInfoCache = new Dictionary<Type, FieldInfo[]>();
private static readonly Dictionary<FieldInfo, dynamic> compiledDelegates = new Dictionary<FieldInfo, dynamic>();
private static T Lerp<T>(T start, T end, float amount) where T : new()
{
    FieldInfo[] fields;
    var type = typeof(T);

    if(!fieldInfoCache.TryGetValue(type, out fields))
    {
        fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);

        fieldInfoCache.Add(type, fields);
    }

    var binds = new List<MemberBinding>();

    foreach(var fieldInfo in fields)
    {
        dynamic getter;

        if(!compiledDelegates.TryGetValue(fieldInfo, out getter))
        {
            var targetExp = Expression.Parameter(type, type.Name);
            var fieldExp = Expression.Field(targetExp, fieldInfo);

            getter = Expression.Lambda(typeof(Func<,>).MakeGenericType(type, fieldInfo.FieldType), fieldExp, targetExp).Compile();

            compiledDelegates.Add(fieldInfo, getter);
        }

        var startVal = getter.Invoke(start);
        var endVal = getter.Invoke(end);

        //This needs to be assigned to something
        var newVal = fieldInfo.FieldType.IsAssignableFrom(typeof(float)) ? LerpF(startVal, endVal, amount) : Lerp(startVal, endVal, amount);

        var fieldParamExp = Expression.Parameter(fieldInfo.FieldType, "newVal");
        var bind = Expression.Bind(fieldInfo, fieldParamExp);

        binds.Add(bind);
    }

    //How do I fix these two lines?
    var memberInit = Expression.MemberInit(Expression.New(type), binds);
    var result = Expression.Lambda<Func<T>>(memberInit).Compile().Invoke();

    return result;
}

我被难住的部分是如何将值输入最后两行而不导致装箱

【问题讨论】:

  • 那么,您尝试创建的代理应该为每个float 字段调用LerpF() 吗?在这种情况下,您需要创建一个调用该方法的表达式,而不是直接调用它。
  • 是的,没错,但为什么需要在表达式中完成呢?我想也许如果我将这一切捆绑到一个表达式中......但我计划有一组表达式用于从字段中获取数据,一组表达式用于构造新对象。除非这可能使构建部分更容易?

标签: c# c#-4.0 dynamic expression-trees


【解决方案1】:

代替

var fieldParamExp = Expression.Parameter(fieldInfo.FieldType, "newVal");

尝试使用

var fieldParamExp = Expression.Constant(newVal);

更新:

为了有效的缓存,你可以使用类似的东西

        var startPar = Expression.Parameter(typeof (T), "start");
        var endPar = Expression.Parameter(typeof (T), "end");
        var amountPar = Expression.Parameter(typeof (float), "amount");
        foreach (var fieldInfo in fields)
        {
            MethodInfo mi;
            if (fieldInfo.FieldType.IsAssignableFrom(typeof (float)))
            {
                mi = typeof (Program).GetMethod("LerpF");
            }
            else
            {
                mi = typeof (Program).GetMethod("Lerp").MakeGenericMethod(fieldInfo.FieldType);
            }

            var makeMemberAccess = Expression.Call(mi, Expression.MakeMemberAccess(startPar, fieldInfo), Expression.MakeMemberAccess(endPar, fieldInfo), amountPar);
            binds.Add(Expression.Bind(fieldInfo, makeMemberAccess));
        }

        var memberInit = Expression.MemberInit(Expression.New(type), binds);
        var expression = Expression.Lambda<Func<T, T, float, T>>(memberInit, startPar, endPar, amountPar);
        Func<T, T, float, T> resultFunc = expression.Compile();
        // can cache resultFunc

        var result = resultFunc(start, end, amount);

但我不知道你是如何决定使用 start 或 end 参数的,所以绑定中可能会有一些更复杂的条件。

【讨论】:

  • 我一开始就是这样做的,但问题是它不能缓存。我每次都必须编译一个新表达式,这会破坏使用表达式的目的。
  • 你如何决定选择 startVal 还是 leftVal?
  • 它是一个 lerp 函数。 startVal 和 endVal 结合在一起,产生 newVal,这是需要传递给构造函数的。
  • 您的编辑看起来只假设 T 上有 2 个字段,但它是一个可变数字。在构造对象时,T 可以有 1 到 10 个需要设置的字段
  • 这里还有一个变种,根据字段类型决定使用 Lerp / LerpF
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-15
  • 2021-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多