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 ;-)
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 {
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
):
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
const int _LoopCount = 9999999;
Type _tpFoo = typeof(Foo);
for (var i = _LoopCount; i-- > 0; ) ;
var anonymous = (Func<Foo, int, int>)((foo, x) => foo.Mult3(x));
for (var i = _LoopCount; i-- > 0; ) { var y = anonymous(_Foo, 3); }
var inf = _tpFoo.GetMethod("Mult3");
for (var i = _LoopCount; i-- > 0; ) { var y = (int)inf.Invoke(_Foo, new object[] { 3 }); }
anonymous = inf.MakeCompiledMethod<Func<Foo, int, int>>();
for (var i = _LoopCount; i-- > 0; ) { var y = anonymous(_Foo, 3); }
var anonymous = (Action<Foo>)(foo => foo.Nop());
for (var i = _LoopCount; i-- > 0; ) anonymous(_Foo);
var inf = _tpFoo.GetMethod("Nop");
for (var i = _LoopCount; i-- > 0; ) inf.Invoke(_Foo, null);
anonymous = inf.MakeCompiledMethod<Action<Foo>>();
for (var i = _LoopCount; i-- > 0; ) anonymous(_Foo);
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:
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 {
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 public static T MakeCompiledMethod<T>(this MethodInfo mtd) {
18 object dlg = mtd.MakeCompiledMethod();
19 try { return (T)dlg; }
20 catch (InvalidCastException x) {
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 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 ParameterExpression
s
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 MethodInfo
s 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: MethodInfo
s 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.