使用表达式树构造具有未知成员的对象

时间:2013-12-15 11:11:32

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

我正在尝试创建一个泛型函数,该函数将使用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;
}

我难以理解的部分是如何在不引起装箱的情况下将值输入最后2行

1 个答案:

答案 0 :(得分:2)

而不是

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参数的,所以在绑定中可能会有一些更复杂的条件。