Search code examples
c#linqexpression

Get property value with Expression without knowing target type at compile time


I'm trying to create an expression lambda to pass an object, and then get the value for named property return. However the type is only known at runtime.

I started with the following method to handle types known at compile time:

private static Func<T, object> CreateExpression(string propertyName)
{
    var arg = Expression.Parameter(typeof(T));
    var expr = Expression.Property(arg, propertyName);
    return Expression.Lambda<Func<T, object>>(expr, arg).Compile();
}

Which worked perfect. However, i need to change it to handle types only known at runtime.

I should be able to call the delegate like this:

public object GetPropertyValue(object obj)
{
    var propertyDelegate = GetDelegate(typeof(obj));        
    var propertyValue = propertyDelegate (obj);
    return propertyValue;
}

private Func<object, object> GetDelegate(Type type)
{
    // Lookup delegate in dictionary, or create if not existing
    return CreateDelegate("MyProperty", type);
}

I tried changing the CreateDelegate from before, but it will not work with Func<object, object>:

Func<object,object> CreateDelegate(string propertyName, Type targetType)
{
    var arg = Expression.Parameter(type);
    var body = Expression.Property(arg, name);

    var lambda = Expression.Lambda<Func<object,object>>(body, arg); //ArgumentException
    return lambda.Compile();
}

It will not accept the Expresion.Parameter, since it is of type 'targetType', and not of type 'object'.

Do i need a Expression.Convert or something?

NOTE: The delegate will be called many times (Filtering method), so it need to be compiled, to ensure performance.

EDIT: Solution (provided by Marc Gravell)

the variable 'body' should be changed to the following:

var body = Expression.Convert(
             Expression.Property(
               Expression.Convert(arg, type), 
               name), 
             typeof(object));

Inner Convert converts input parameter to object, and the outer Convert converts the return value.


Solution

  • Yes:

    var arg = Expression.Parameter(typeof(object));
    var expr = Expression.Property(Expression.Convert(arg, type), propertyName);
    

    Note: the return type (object) means that many types will need to be boxed. Since you mention you are doing this for filtering: if possible, try to avoid this box by creating instead a Func<object,bool> that does any comparisons etc internally without boxing.