Frage

I'm trying to create a generic function that will take 2 instances of a struct and create a new instance using the values of the passed-in instances. I'm mostly there but I'm having trouble figuring out how to build the expression tree to take the new values as parameters in the MemberInit (first time using expression trees).

I'm trying to avoid creating garbage (so no boxing) as much as possible.

Here's what I have so far:

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;
}

The part that I'm stumped on is how to feed the values into those last 2 lines without causing boxing

War es hilfreich?

Lösung

Instead of

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

try use

var fieldParamExp = Expression.Constant(newVal);

Update:

For efficient cache you could use something like

        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);

But I don't know how you decide to use start or end parameter, so there may be some more complex conditions in bindings.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top