views:

444

answers:

2

I'm generating compiled getter methods at runtime for a given member. Right now, my code just assumes that the result of the getter method is a string (worked good for testing). However, I'd like to make this work with a custom converter class I've written, see below, "ConverterBase" reference that I've added.

I can't figure out how to add the call to the converter class to my expression tree.

    public Func<U, string> GetGetter<U>(MemberInfo info)
    {
        Type t = null;
        if (info is PropertyInfo) 
        {
            t = ((PropertyInfo)info).PropertyType;
        }
        else if (info is FieldInfo)
        {
            t = ((FieldInfo)info).FieldType;
        }
        else
        {
            throw new Exception("Unknown member type");
        }

        //TODO, replace with ability to specify in custom attribute
        ConverterBase typeConverter = new ConverterBase();

        ParameterExpression target = Expression.Parameter(typeof(U), "target");
        MemberExpression memberAccess = Expression.MakeMemberAccess(target, info);

        //TODO here, make the expression call "typeConverter.FieldToString(fieldValue)"

        LambdaExpression getter = Expression.Lambda(memberAccess, target);

        return (Func<U, string>)getter.Compile();
    }

I'm looking for what to put in the second TODO area (I can handle the first :)).

The resulting compiled lambda should take an instance of type U as a param, call the specified member access function, then call the converter's "FieldToString" method with the result, and return the resulting string.

+3  A: 

You need to wrap the object in an ExpressionConstant, e.g. by using Expression.Constant. Here's an example:

class MyConverter
{
    public string MyToString(int x)
    {
        return x.ToString();
    }
}

static void Main()
{
    MyConverter c = new MyConverter();

    ParameterExpression p = Expression.Parameter(typeof(int), "p");
    LambdaExpression intToStr = Expression.Lambda(
        Expression.Call(
            Expression.Constant(c),
            c.GetType().GetMethod("MyToString"),
            p),
        p);

    Func<int,string> f = (Func<int,string>) intToStr.Compile();

    Console.WriteLine(f(42));
    Console.ReadLine();
}
Barry Kelly
Expression.Constant - FTW. Thanks. I'll try it out now and award the win if it's good. Thanks!
TheSoftwareJedi
you missed my nested member access call, but that was easy enough to add to the tree. Thanks again
TheSoftwareJedi
I didn't miss it - I believed I saw your dilemma and addressed that specifically :) I work on compilers in my day job, so it was pretty clear.
Barry Kelly
You missed the cast, which leaves me split between giving you the answer or Marc... Given his included both the cast, call, and member access, I'm going to toss them his way. Cheers.
TheSoftwareJedi
You're welcome :)
Barry Kelly
+1 from me too - good to see somebody else who can talk "Expression" ;-p
Marc Gravell
+2  A: 

Can you illustrate what (if it was regular C#) you want the expression to evaluate? I can write the expression easily enough - I just don't fully understand the question...

(edit re comment) - in that case, it'll be something like:

    ConverterBase typeConverter = new ConverterBase();
    var target = Expression.Parameter(typeof(U), "target");
    var getter = Expression.MakeMemberAccess(target, info);
    var converter = Expression.Constant(typeConverter, typeof(ConverterBase));

    return Expression.Lambda<Func<U, string>>(
    Expression.Call(converter, typeof(ConverterBase).GetMethod("FieldToString"),
        getter), target).Compile();

Or if the type refuses to bind, you'll need to inject a cast/convert:

    MethodInfo method = typeof(ConverterBase).GetMethod("FieldToString");
    return Expression.Lambda<Func<U, string>>(
        Expression.Call(converter, method,
            Expression.Convert(getter, method.GetParameters().Single().ParameterType)),
            target).Compile();
Marc Gravell
Perfect. Works like a charm. I ran up against the cast problem, but came back here and saw this answer... Thanks!
TheSoftwareJedi