jeudi 13 août 2020

Build a compiled delegate corresponding to Enumerable.Cast

This is very tricky and I'm stuck at the step calling the generic method (MethodInfo) which is returned by another MethodCallExpression (by using MakeGenericMethod) right inside the expression tree context.

Technically the compiled delegate I want looks like this:

Func<IEnumerable, Type, IEnumerable> cast;

So instead of using items.Cast<T>() I can call my compiled delegate like cast(items, typeof(T)).

If using reflection every time calling cast, it would be easy but here I would like to build a compiled delegate based on Expression tree. Here is my code:

public static class EnumerableExtensions {
    static readonly Func<IEnumerable, IEnumerable<object>> _enumerableCast = Enumerable.Cast<object>;
    static readonly Lazy<MethodInfo> _enumerableCastDefLazy = new Lazy<MethodInfo>(() => _enumerableCast.Method.GetGenericMethodDefinition());
    static MethodInfo _enumerableCastDef => _enumerableCastDefLazy.Value;
    static Func<Type[], MethodInfo> _makeGenericMethod = _enumerableCastDef.MakeGenericMethod;
    static readonly Lazy<Func<IEnumerable, Type, IEnumerable>> _enumerableCompiledCastLazy =
        new Lazy<Func<IEnumerable, Type, IEnumerable>>(() => {
            var itemsParam = Expression.Parameter(typeof(IEnumerable));
            var castTypeParam = Expression.Parameter(typeof(Type));
            var castTypeParams = Expression.NewArrayInit(typeof(Type), castTypeParam);
            var castMethod = Expression.Call(Expression.Constant(_enumerableCastDef),_makeGenericMethod.Method, castTypeParams);

            //here we need to call on castMethod (a static method)
            //but the Expression.Call requires a MethodInfo, not an Expression returning MethodInfo
            var cast = Expression.Call(..., itemsParam);//<--------- I'm stuck here

            return Expression.Lambda<Func<IEnumerable, Type, IEnumerable>>(cast, itemsParam, castTypeParam).Compile();
        });
    public static Func<IEnumerable, Type, IEnumerable> EnumerableCompiledCast => _enumerableCompiledCastLazy.Value;

    public static IEnumerable Cast(this IEnumerable items, Type type){
        return EnumerableCompiledCast(items, type);
    }
}

So as you can see it's really a dead stuck, never encountered such an issue like this before. I know I can work-around it by invoking the castMethod (as a MethodCallExpression). That way I need to obtain the Invoke method of MethodInfo and use Expression.Call to call that method on the instance castMethod. But wait, if so we still use Method.Invoke as we use Reflection to write code usually without compiling it? I really believe in some hidden magic of Expression.Call which does something different (better and faster) than the MethodInfo.Invoke.





Aucun commentaire:

Enregistrer un commentaire