0
votes

I have some properties in many classes and want to compile getter/setter of them into lambda expressions so that I can use reflections with better performance.

(Profiled, and using Reflection's getValue/setValue takes up about 78% of total running time...)

However It seems that Expression.Lambda() Only supports one collection of parameter and will not convert parameter type automatically.

using Exp = System.Linq.Expressions.Expression;
...
public class A { public int propA { get; set; } }
public class B { public int propB { get; set; } }
static Func<object, int> BuildFunc(Type type, string propName)
{
    var param = Exp.Parameter(prop.DeclaringType, "x");
    var exBody = Exp.Call(param, prop.GetGetMethod());
    return Exp.Lambda<Func<object, int>>(exBody, param).Compile();
}
...
var a = new A();
var b = new B();
var fA = BuildFunc(typeof(A).GetProperty("propA"));
var fB = BuildFunc(typeof(B).GetProperty("propB"));
fA(a);
fB(b);

It will thorw an exception:

ParameterExpression of type __Main__+A cannot be used for delegate parameter of type System.Object

If I change the expression into Exp.Lambda<Func<A, int>>(...) it will work with class A but will not work with class B.

If I use Expression.Convert to convert types, it will throw ArgumentException that tells me the method cannot invoke on instance of System.Object.

So what can I do to compile this expression just like below, which supports any type of object and corresponding method? lambda = (object obj, MethodInfo method, ...) => { method.Invoke(obj, ...) }

1

1 Answers

0
votes

There are better expression APIs available for invoking property getter/setter methods. You definitely don't have to resort to MethodInfo.Invoke.

The conversion from/to object is handled by Expression.Convert. You just need to plug it in the right spot.

Here is a getter/setter delegate compilation example where T is the type of your container (A or B in your example).

static Func<T, object> CompileGetter<T>(PropertyInfo property)
{
    // Target expression: (T obj) => (object)obj.Property;
    ParameterExpression objParam = Expression.Parameter(typeof(T), "obj");
    Expression body = Expression.Convert(Expression.MakeMemberAccess(objParam, property), typeof(object));

    return Expression
        .Lambda<Func<T, object>>(body, objParam)
        .Compile();
}

static Action<T, object> CompileSetter<T>(PropertyInfo property)
{
    // Target expression: (T obj, object value) => obj.Property = (TProperty)value;
    ParameterExpression objParam = Expression.Parameter(typeof(T), "obj");
    ParameterExpression valueParam = Expression.Parameter(typeof(object), "value");

    Expression body = Expression.Assign(
        Expression.MakeMemberAccess(objParam, property),
        Expression.Convert(valueParam, property.PropertyType)
    );

    return Expression
        .Lambda<Action<T, object>>(body, objParam, valueParam)
        .Compile();
}

Proof:

class Dummy
{
    public int Value { get; set; }
}

...

PropertyInfo prop = typeof(Dummy).GetProperty("Value");
Func<Dummy, object> getter = CompileGetter<Dummy>(prop);
Action<Dummy, object> setter = CompileSetter<Dummy>(prop);

Dummy d = new Dummy { Value = 123 };

Assert.AreEqual(123, getter(d));

setter(d, 321);

Assert.AreEqual(321, d.Value);

Please note: LambdaExpression.Compile is an extremely CPU-intensive operation, so once you create your getter/setter delegate, you must cache it in order to get that performance boost you're looking for.