Wednesday, June 4, 2014

Retrieving Generic Method Definitions

I had encountered this problem at least a couple of times, and have never really found an elegant solution for it, up until a couple of days ago. The problem is very simple. I need to for example retrieve the
public static IQueryable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)  
method definition. Since the method has quite a lot of generic arguments, and they are themselves complex types, it is quite a pain to retrieve it with a reflection search. When I googled, i found solutions like:
           var methodDefinition = typeof(Queryable).GetMethods()  
               .Where(x => x.Name == "GroupBy")  
               .Select(x => new { M = x, P = x.GetParameters() })  
               .Where(x => x.P.Length == 2  
                 && x.P[0].ParameterType.IsGenericType  
                 && x.P[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)  
                 && x.P[1].ParameterType.IsGenericType  
                 && x.P[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))  
               .Select(x => new { x.M, A = x.P[1].ParameterType.GetGenericArguments() })  
               .Where(x => x.A[0].IsGenericType  
                 && x.A[0].GetGenericTypeDefinition() == typeof(Func<,>))  
               .Select(x => new { x.M, A = x.A[0].GetGenericArguments() })  
               .Where(x => x.A[0].IsGenericParameter  
                 && x.A[1].IsGenericParameter)  
               .Select(x => x.M)  
               .SingleOrDefault();  
I mean really? are you kidding me? There has to be an easier way. This is waaaaay to complicated.
Well, i have good news, it is all not needed. Why would we need to do all this reflection heavy lifting, if Microsoft had made a tool that already does this? Yep, you guessed, it is the compiler.

Elegant solution

So what am I talking about? Very simple.
When you define a lambda expression, the compiler actually builds up the expression for you. A lambda expression is the definition of the code inside, and if you build an expression with the desired method, well, it will be in the expression for you to retrieve.
So how does the other code translate?
           Expression<Func<IQueryable<string>, IQueryable<IGrouping<bool, string>>>> fakeExp = q => q.GroupBy(x => x.StartsWith(string.Empty));  
           var methodDefinition = ((MethodCallExpression)fakeExp.Body).Method.GetGenericMethodDefinition();  
Since from the expression we use only the method definition, obviously the types do not matter. We get the generic method definition, and we get from it the desired version by calling the MakeGenericMethod method. The generic method definition you can statically cache, and then create a simple method that will get you the desired type, like:
       private static MethodInfo _queryableGroupByMethod;  
       public static MethodInfo QueryableGroupByMethod  
       {  
         get  
         {  
           if (_queryableGroupByMethod == null)  
           {  
             Expression<Func<IQueryable<string>, IQueryable<IGrouping<bool, string>>>> fakeExp = q => q.GroupBy(x => x.StartsWith(string.Empty));  
             _queryableGroupByMethod = ((MethodCallExpression)fakeExp.Body).Method.GetGenericMethodDefinition();  
           }  
           return _queryableGroupByMethod;  
         }  
       }  
       public static MethodInfo MakeQueryableGroupByMethod(Type source, Type destination)  
       {  
         return QueryableGroupByMethod.MakeGenericMethod(source, destination);  
       }  
That's it. I guess the pattern is clear, you can use it for any other method. No strings, no comlicated selects and wheres. Just pure, compiler checked goodies :)

No comments:

Post a Comment