Reputation: 4763
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
.
Upvotes: 0
Views: 134
Reputation: 67380
What you're trying to do is completely pointless, and very unlike Enumerable.Cast
, which actually does something useful.
Let's take a look at the latter's definition:
IEnumerable<T> Cast<T>(this IEnumerable source);
This takes an untyped IEnumerable
and returns a typed IEnumerable<T>
based on the generic argument given to the function. You can then use the elements inside the enumerable with the proper type directly, including value types.
Now let's look at your function definition:
IEnumerable Cast(this IEnumerable items, Type type);
This takes an untyped enumerable and returns also an untyped enumerable. What it does inside isn't important, because even if it worked as you want, what you get out of this is still an enumerable of plain object
s, so to use these values you still need to cast these things correctly (and unbox the boxed value types). You achieved nothing at all, you already had such a collection -- the thing you passed to your function in the first place.
Even if you make the cast work using a cache of compiled expressions, one per type, which isn't hard to do, the output is still cast back to object
by your very return type.
Upvotes: 2