Search code examples
c#reflectionlambdaexpression-trees

Moving from reflection to expression-tree


I have a reflection code, to create an instance of List<> (type parameter known at run-time), and call Add method to add some values to it. My snippet is something like this:

// here is my type parameter
var genericType = typeof(MyRunTimeType);
// here is a list of my values
MyRunTimeType[] values = MyRunTimeValuesOfTypeMyRunTimeType();

// creating instance of List<>
var listType = typeof(List<>);
var listGenericType = listType.MakeGenericType(genericType);
var listInstance = Activator.CreateInstance(listGenericType);

// getting Add method and call it
var addMethod = listGenericType.GetMethod("Add", genericType);
foreach (var value invalues)
    addMethod.Invoke(listInstance, new[] { value });

So, how would you suggest to convert this reflection snippet to an expression-tree?

UPDATE:

Well, I wrote this snippet, which seems closed to work:

public static Func<IEnumerable<object>, object> GetAndFillListMethod(Type genericType) {

    var listType = typeof(List<>);
    var listGenericType = listType.MakeGenericType(genericType);

    var values = Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(genericType), "values");

    var ctor = listGenericType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null);
    var instance = Expression.Parameter(listGenericType, "list");

    var assign = Expression.Assign(instance, Expression.New(ctor));

    var addMethod = listGenericType.GetMethod("AddRange", new[] { typeof(IEnumerable<>).MakeGenericType(genericType) });

    var addCall = Expression.Call(instance, addMethod, new Expression[] { values });

    var block = Expression.Block(
          new[] { instance },
          assign,
          addCall,
          Expression.Convert(instance, typeof(object))
        );

    return (Func<IEnumerable<object>, object>)Expression.Lambda(block, values).Compile();
}

But, I'm getting this error:

Unable to cast object of type 
'System.Func`2[System.Collections.Generic.IEnumerable`1[System.String],System.Object]' 
to type 
'System.Func`2[System.Collections.Generic.IEnumerable`1[System.Object],System.Object]'.

Any suggestion please?


Solution

  • Working:

    public static Func<IEnumerable<object>, object> GetAndFillListMethod(Type genericType)
    {
        var listType = typeof(List<>);
        var listGenericType = listType.MakeGenericType(genericType);
    
        var values = Expression.Parameter(typeof(IEnumerable<object>), "values");
    
        var ctor = listGenericType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null);
    
        // I prefer using Expression.Variable to Expression.Parameter
        // for internal variables
        var instance = Expression.Variable(listGenericType, "list");
    
        var assign = Expression.Assign(instance, Expression.New(ctor));
    
        var addMethod = listGenericType.GetMethod("AddRange", new[] { typeof(IEnumerable<>).MakeGenericType(genericType) });
    
        // Enumerable.Cast<T>
        var castMethod = typeof(Enumerable).GetMethod("Cast", new[] { typeof(IEnumerable) }).MakeGenericMethod(genericType);
    
        // For the parameters there is a params Expression[], so no explicit array necessary
        var castCall = Expression.Call(castMethod, values);
        var addCall = Expression.Call(instance, addMethod, castCall);
    
        var block = Expression.Block(
                new[] { instance },
                assign,
                addCall,
                Expression.Convert(instance, typeof(object))
            );
    
        return (Func<IEnumerable<object>, object>)Expression.Lambda(block, values).Compile();
    }
    

    Your problem is in the fact that you are trying to return a Func<IEnumerable<object>, object> but your func is in truth a Func<IEnumerable<T>, object>. The solution is to make the parameter a IEnumerable<object> and then use Enumerable.Cast<T> before passing to AddRange

    I've changed the Expression.Parameter used for instance to a Expression.Variable... But it is only to make it more clear that it is a variable, not a parameter. The expression tree generated by Expression.Variable and by Expression.Parameter is the same (because the two functions have the same code). It is the context where it is used that defines if it is a parameter or a variable. I've done another small change: Expression.Call doesn't need an explicit array initialization for the parameters.

    Ah... And note that the last line of the Block could be:

    addCall,
    instance
    

    instead of being

    addCall,    
    Expression.Convert(instance, typeof(object))
    

    because any reference type is implicitly convertible to object.