3

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

2
  • So, the delegate you're trying to create should call LerpF() for each float field? In that case, you need to create an expression that calls that method, not just call it directly. Commented Dec 15, 2013 at 11:58
  • Yep, that's correct, but why does that need to be done inside the expression? I guess maybe if I bundled this all into 1 expression maybe... but I was planning on having 1 set of expressions for getting the data from the fields, and 1 set for constructing the new object. Unless maybe that makes the constructing part easier? Commented Dec 15, 2013 at 12:14

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

6 Comments

That's what I did at first, but the problem is that it wouldn't be cache-able then. I'd have to compile a new expression every time, which would defeat the purpose of using expressions.
How do you decide what to choose startVal or leftVal?
Its a lerp function. startVal and endVal are lerped together, producing newVal, which is what needs to be passed to the constructor.
Your edit looks like it assumes only 2 fields on T but its a variable number. T could have anywhere from 1 to 10 fields that need to be set when constructing the object
Here is one more variant, with decide to use Lerp / LerpF based on field type
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.