Search code examples
c#expression-trees

Call Select Method on List created using Expression and use a passed Func<> within the Select method


I simply want to do this:

(inputObj ) => (inputObj .Select(objEln=> hubObjectConverter(objEln)));

inputObj ----> List<elnObject> 

hubObjectConverter ----> Func<object,object>

Where am I going wrong?

var typeElnObjList = typeof(List<>).MakeGenericType(new[] { elnObjectType });
var inputObj = Expression.Parameter(typeElnObjList, "lstElnObj");

var paramSelectMeth = Expression.Parameter(elnObjectType, "objEln");
var convertToObject = Expression.Invoke(Expression.Constant(hubObjectConverter), paramSelectMeth);
var lambdaSelect = Expression.Lambda(convertToObject, paramSelectMeth);

var convertList = Expression.Call(typeof(Enumerable), 
                                   "Select", 
                                   new[] { elnObjectType, hubObjectType }, 
                                   inputObj, 
                                   lambdaSelect);  <------ I keep getting an error here. Saying Select cannot accept generic type. Where am I going wrong?

Solution

  • (I assume your question is an X/Y problem, so I won't answer your question at face-value)

    If your intent is to allow converting from a List<TIn> to a List<TOut> by specifying the type of TOut at runtime with a Type rather than a generic-type parameter then you only need MakeGenericMethod and you don't need to use Expression<> at all:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    
    public static class ListExtensions
    {
        public static IList ConvertList<TSource>( this List<TSource> source, Type destinationType, Func<object,object> hubObjectConverter )
        {
            if( source is null ) throw new ArgumentNullException(nameof(source));
            if( destinationType is null ) throw new ArgumentNullException(nameof(destinationType));
            
            //
            
            MethodInfo mi = typeof(ListExtensions)
                .GetMethod( nameof(ConvertListImpl), BindingFlags.Static | BindingFlags.NonPublic )
                .MakeGenericMethod( typeof(TSource), destinationType );
    
            Object result = mi.Invoke( obj: null, new Object[] { source, hubObjectConverter } );
            return (IList)result;
        }
    
        private static List<TOut> ConvertListImpl<TIn,TOut>( List<TIn> source, Func<Object,Object> converter )
        {
            return source
                .Select( item => converter( item ) )
                .Cast<TOut>()
                .ToList();
        }
    }
    

    (To improve performance, the MethodInfo could be cached in a static readonly ConcurrentDictionary<(TIn,TOut),MethodInfo> dictionary).

    It would be used like so:

    List<Int32> listOfInt32 = new List<Int32>() { 1, 2, 3, 4, 5 };
    
    IList listOfString = listOfInt32.ConvertList( destinationType: typeof(String), obj => obj.ToString() );
    

    Even though listOfString is statically-typed as IList, its actual runtime type is List<String>.