Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

generic Method-access via Linq.Expressions instead of Reflection

4.45/5 (13 votes)
28 Oct 2017CPOL5 min read 19.2K   116  
10-times faster Accesss with Reflection.MethodInfo (updated)

Sorry - the attached Code has turned out as outdated

Please first refer to Dismembers Article-Comment He gives the advice, that what I have developed by myself, to "compile" MethodInfos to anonymous methods, using Linq.Expression - that already exists as Member of the MethodInfo-Class - namly as MethodInfo.CreateDelegate<TDelegate>().

Now I leave the article as it was - maybe you have fun to see a bit of operating on Linq.Expressions, and glimpse a bit to some of its concepts.

But don't take the attached sources as practical problem-solving of anything. Instead of my
MethodInfo.MakeCompiledMethod<TDelegate>() - Extension-Method
imperativly simply use the built-in
MethodInfo.CreateDelegate<TDelegate>() - Instance-Method.

Maybe now I should delete this article, but as the readers interests shows, only very few programmers know MethodInfo.CreateDelegate<TDelegate>()

So although the article in a way has lost it's code to share, it still may help increase the level of awareness of MethodInfo.CreateDelegate() - which can be seen as a helpful service to the communities Knowledge too.

But now the article as it was, before I learned to know .CreateDelegate():

Code First ;-)

C#
public static Delegate MakeCompiledMethod(this MethodInfo mtd) {
   if (mtd == null) throw new ArgumentNullException("ReflectionX.MakeCompiledMethod(MethodInfo mtd): mtd mustn't be null");
   var prams = mtd.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToList();
   Expression methodCall;
   if (mtd.IsStatic) methodCall = Expression.Call(null, mtd, prams);
   else {   // on instance-Methods the ownerInstance must be included
      var ownerInstance = Expression.Variable(mtd.DeclaringType, "ownerInstance");
      methodCall = Expression.Call(ownerInstance, mtd, prams);
      prams.Insert(0, ownerInstance);
   }
   return Expression.Lambda(methodCall, false, prams).Compile();
}   

Then Benchmarks

TestClass Foo, with 2 Methods (void and int):

C#
class Foo {
   public void Nop() { }
   public int Mult3(int x) { return x * 3; }
}

Benchmark-Code: First empty run, then call Foo.Nop() / Foo.Mult3() 10000000 times, applying three different kinds of call:

  • DelegateExec - call by hardcoded anonymous method
  • MethodInfoExec - generic call by MethodInfo.Invoke()
  • CompiledInfExec - generic call by  anonymous method, which was built by compiling the MethodInfo
C#
const int _LoopCount = 9999999;
Type _tpFoo = typeof(Foo);
for (var i = _LoopCount; i-- > 0; ) ;                                                                // EmptyRun - #0

var anonymous = (Func<Foo, int, int>)((foo, x) => foo.Mult3(x));
for (var i = _LoopCount; i-- > 0; ) { var y = anonymous(_Foo, 3); }                          // DelegateExec Mult3 #1

var inf = _tpFoo.GetMethod("Mult3");			// MethodInfo of Mult3()

for (var i = _LoopCount; i-- > 0; ) { var y = (int)inf.Invoke(_Foo, new object[] { 3 }); }  // MethodInfoExec Mult3 #2

anonymous = inf.MakeCompiledMethod<Func<Foo, int, int>>();   // inf compiled
for (var i = _LoopCount; i-- > 0; ) { var y = anonymous(_Foo, 3); }                        // CompiledInfExec Mult3 #3

var anonymous = (Action<Foo>)(foo => foo.Nop());
for (var i = _LoopCount; i-- > 0; ) anonymous(_Foo);                                            // DelegateExec Nop #4

var inf = _tpFoo.GetMethod("Nop");			// MethodInfo of Nop() 

for (var i = _LoopCount; i-- > 0; ) inf.Invoke(_Foo, null);                                   // MethodInfoExec Nop #5

anonymous = inf.MakeCompiledMethod<Action<Foo>>();   // inf compiled
for (var i = _LoopCount; i-- > 0; ) anonymous(_Foo);                                         // CompiledInfExec Nop #6

Benchmark-Results: Milliseconds, to execute Foo.Nop() / Foo.Mult3() 10000000 times:

#0 - Executing EmptyRun: 48
#1 - Executing DelegateExec    Mult3:  432
#2 - Executing MethodInfoExec  Mult3: 5706
#3 - Executing CompiledInfExec Mult3:  419
#4 - Executing DelegateExec    Nop:  153
#5 - Executing MethodInfoExec  Nop: 3030
#6 - Executing CompiledInfExec Nop:  275

You see: Using Reflection - "MethodInfoExec" is the slowest. Doing it with compiled MethodInfo - "CompiledInfExec" - is 10 times faster.
Applied on the Method with return-value "CompiledInfExec" is even faster (a very little bit) than the hardcoded anonymous method - "DelegateExec"! - I have no idea why.

(Here I could finish, recommend the attached sources, and it wouldn't be the worst codeproject-tip of all, I think)

System.Linq.Expressions

But let me take the opportunity to give a short, simplyfied and incorrect introduction to the used technology - namely the System.Linq.Expressions.Expression-Class and its Derivates.
Together it is a System, which models a complete Programming-Language - maybe it's the Common Intermediate Language (CIL) itself, which is modelled.

Just to get an imagination of the provided power, see some of the (method-)names:

Add(); AddAssign(); And(); AndAlso(); ArrayAccess(); ArrayIndex(); ArrayLength(); Assign(); Block(); Break(); Call(); Catch(); 
ClearDebugInfo(); Coalesce(); Condition(); Constant(); Continue(); Convert(); DebugInfo(); Decrement(); Default(); Divide(); DivideAssign(); 
Dynamic(); ElementInit(); Empty(); Equal(); ExclusiveOr(); Field(); Goto(); GreaterThan(); GreaterThanOrEqual(); IfThen(); IfThenElse(); 
Increment(); Invoke(); IsFalse(); IsTrue(); Lambda(); LeftShift(); LessThan(); LessThanOrEqual(); ListInit(); Loop(); MakeBinary(); 
MakeCatchBlock(); MakeDynamic(); MakeGoto(); MakeIndex(); MakeMemberAccess(); MakeTry(); MakeUnary(); Modulo(); Multiply(); Negate();
ewArrayBounds(); Not(); NotEqual(); Or(); OrElse(); Parameter(); PostIncrementAssign(); Power(); Property(); PropertyOrField(); 
ReferenceEqual(); ReferenceNotEqual(); Rethrow(); Return(); RightShift(); Subtract(); Switch(); Throw(); TryCatch(); TypeAs(); TypeEqual(); 
TypeIs(); UnaryPlus(); Variable(); 

Surely you recognize equivalences for almost all c#-Keywords and Operators - that is not by accident.

Don't worry: You don't have to handle all of this, or even understand it - most of you will never deal with anything of it.

But in some particular cases it can be useful to pick a small piece of it and make it work.
For instance my "Expression-Exercise" (see the "Code First" - Listing) takes information from an arbitrary MethodInfo to build an anonymous method which calls the by the MethodInfo described Method, but without Reflection, means: fast.
As the Benchmark shows, it may be still 2 times slower than a hard-coded anonymous method, but is about 10 times faster than doing it with Reflection.

Some points of Interest on Expressions

  • Tree-Structure
    Expression (and Derivate)-Instances are designed to be composed together to complex Trees of arbitrary Size - the Expression-Tree
  • No Constructor
    Expression-Instances can't be created with new-Keyword. Instead of that use one the many static generator-Methods - as listed above (and there are some more, and lots of overloads).
  • Building an Expression-Tree is slow
    It takes some milliseconds, especially compiling. What is fast is using the result-Delegate. So for just one or two method-calls this approach is very inappropriate - better stay with Reflection.

the Code in Detail

The first listing is only half of the truth. Its drawback is, that it returns an arbitrary anonymous method of Type Delegate - which is abstract, and not useable for anything. You must cast it to a concrete Delegate-Type, like Action<T>, Func<T1, TResult> -  or whatever.
The Problem is evident: Without knowing the concrete signature you can't call a method (ok - you can - but not with success).
For that I wrapped the core-method into another, which only cares about the casting from abstract Delegate to concrete, and especially ensures a meaningfull InvalidCastException on failure.
Believe it or not: That wrapper-Method was harder to develop than the wrapped core - see all together:

C#
  1  public static Delegate MakeCompiledMethod(this MethodInfo mtd) {
  2     if (mtd == null) throw new ArgumentNullException("ReflectionX.MakeCompiledMethod(MethodInfo mtd): mtd mustn't be null");
  3     var prams = mtd.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToList();
  4     Expression methodCall;
  5     if (mtd.IsStatic) methodCall = Expression.Call(instance: null, method: mtd, arguments: prams);
  6     else {   // on instance-Methods the ownerInstance must be included
  7        var ownerInstance = Expression.Variable(mtd.DeclaringType, "ownerInstance");
  8        methodCall = Expression.Call(ownerInstance, mtd, prams);
  9        prams.Insert(0, ownerInstance);
 10     }
 11     return Expression.Lambda(methodCall, false, prams).Compile();
 12  }
 13  /// <summary> generates an anonymous Method to call the MethodInfo-method nearly as fast as direct calls. 
 14  /// Eg an (Instance-) MethodInfo "bool StringCollection.Contains(<string>)" would compile to "Func<StringCollection, string, bool>".
 15  /// On the other hand "static bool String.IsNullOrEmpty(<string>)" would compile to "Func<string, bool>".
 16  /// You must specify the correct Delegate-Typparam T to make this method work.</summary>
 17  public static T MakeCompiledMethod<T>(this MethodInfo mtd) {
 18     object dlg = mtd.MakeCompiledMethod();
 19     try { return (T)dlg; }
 20     catch (InvalidCastException x) {//build a proper Exception is more difficult than the function itself
 21        var stpParams = string.Join(", ", mtd.GetParameters().Select(p => p.ParameterType.FriendlyName()));
 22        var sMtd = string.Format("{0} {1}.{2}({3})", mtd.ReturnType.FriendlyName(), mtd.DeclaringType.FriendlyName(), mtd.Name, stpParams);
 23        var sIsStatic = mtd.IsStatic ? "static " : "";
 24        var msg = @"The given MethodInfo ""{0}{1}"" would compile to ""{2}"", but my TypeParameter requests ""{3}""";
 25        msg = string.Format(msg, sIsStatic, sMtd, dlg.GetType().FriendlyName(), typeof(T).FriendlyName());
 26        throw new InvalidOperationException("ReflectionX.MakeCompiledMethod<T>(MethodInfo mtd): " + msg, x);
 27     }
 28  }
 29  /// <summary> a helpful helper </summary>
 30  public static string FriendlyName(this Type tp) {
 31     var rgx = new Regex(@"(?<=(^|[\.\+]))\b\D\w+($|[,`])|\[\[|\]\]");
 32     return string.Concat(rgx.Matches(tp.FullName).Cast<Match>()).Replace("`[[", "<").Replace(",]]", ">").Replace(",", ", ");
 33  }

But I only explain the core (lines #1 - #12) - as an "Exercise in Linq.Expressions" - to recognize what is said in the abovementioned Points of Interest.

#3: get the Method-Parameters and convert them to a List of ParameterExpression
#4: the methodCall-Expression must be pre-declared - because there are two different options to build it:
#5: on static Methods the first argument - "instance" must be null
#7: on instance-Methods we need an additional expression for qualifying the ownerInstance of the instance-Method
#8: pass the arguments to the .Call-call
#9: Our prams-Param-List will be reused when building the compilable Lambda-Expression. Since instance-Methods require as additional Information the ownerInstance, we have to insert it at first position of our params-List.
#11: Create a Lambda-Expression (whatever that means - but it is compilable), compile and return it.

You see: We have built a little tree:
Root is the Lambda, it contains a methodCall-Expression and a List of ParameterExpressions
The methodCall contains a Variable-Expression as Instance, and some Param-Expressions

Lambda - pram1, pram2, ...
      \
       MethodCall - pram2, pram3, ...
                 \
                  ownerInstance (==pram1)

Propably that will be more clear, when you inspect the attached sample-code, using breakpoints and stuff.

Conclusion

My initial goal was simply to share my fancy MethodInfo-Compiler. But then I thought that it doesn't harm, to get at least an idea of the technology behind.
To give an "Entry-Point", if you want develop your own solutions for other, similar problems.

For instance you can take my "MethodInfo-Compiler" as a kind of Template, to develop something similar for Reflection.FieldInfo, or .PropertyInfo

Please note, that the core-method is very stable: Whenever there is a valid MethodInfo (and invalid MethodInfos do not exist) - then it contains all needed data, to create a valid Linq.Expressions from it.
Sorry - minor modification: Presence of ref- or out- Parameters may cause trouble (but laziness prevents me from checking it out in detail).
But what can be said reliable: MethodInfos of Methods with signature of any kind of Action or Func always can be compiled successfully.

An additional Point of Interest may be, that also private Class-Members are accessible by Reflection - and with help of Linq.Expressions that can be well performant.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)