Ninja Ferret

The random thoughts of a software developer

MISL - 5. Lambda Expressions

This is the last of the "tutorial" posts on MSIL. There is a massive amount that I have not had time to cover here but this is, as I said in my first post, the story of my journey through MSIL. You will find the source code for this blog post on my subversion repository. For this one last time I want to carry on the Calculator for one final time. This time I want to explore the use of Lambda expressions and to introduce a pattern similar to the one that I will make use of in later posts. The implementation of the Calculator interface then looks identical for each method:
// Generated class
public class Calc : Calculator
{
	public int Add(int a, int b)
	{
		int result = 0;
		Evaluator.Evaluate(() => result = a + b);
		return result;
	}

	...
}

public delegate void CalcFunction(int a, int b);

public static class Evaluator
{
	public static void Evaluate(CalcFunction codeBlock)
	{
		codeBlock();
	}
}

Defining the Lambda Expression

As always, the first task is to make a test implementation in order use ILDASM to see what has been generated. Looking at the generated type TestCalculator there are four nested types:
  • <>__DisplayClass1
  • <>__DisplayClass4
  • <>__DisplayClass7
  • <>__DisplayClassa
These are the internal representation of the lambda expressions. Each lambda expression has it's own class nested inside the class in which it was defined so it is necessary to generate these nested types using IL. For the most part how a nested type is created is very similar to creating a normal type, it is only how the TypeBuilder is created that is different using the method typeBuilder.DefineNestedType(string.Format("<>__DisplayClass{0}", index, NestedTypeAttributes);. The nested types each have three public fields a, b and result; based on the lambda expression () => result = a + b it should be easy to see where these fields have been derived from. Finally, there is the method which again has a special format, e.g. <Add>b__0 and <Subtract>b__3, which has again been auto-generated based on the name of the method that the lambda expression was defined in and an incrementing number to permit uniqueness. The IL for these methods is very simple, load the two fields (a and b) add/subtract/divide/multiple and save the result in the result field. The DefineLambdaExpression method in my example code creates the nested type, defines the default constructor, defines the fields and implements the method. The final act is to then create the nested type using the TypeBuilder.CreateType() method.

Why can't I use the generated type yet?

Once the nested type has been created it must surely be a usable type? When generating the code that will call this lambda expression I initially expected to be able to use Type.GetConstructor(), Type.GetMethod() and Type.GetField() to get access to the required members of the lambda expression. I was wrong, the first thing I needed to do was to delcare a local of the lambda expression type but I get the exception:
System.TypeLoadException: Could not load type 'Calculator' from assembly 'Calculatorb1d3a726-fa47-47cc-b190-ff5403de4ad7, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
   at System.RuntimeTypeHandle.GetDeclaringType(RuntimeType type)
   at System.RuntimeType.RuntimeTypeCache.GetEnclosingType()
   at System.RuntimeType.get_DeclaringType()
   at System.Reflection.Emit.ModuleBuilder.GetTypeRefNested(Type type, Module refedModule, String strRefedModuleFileName)
   at System.Reflection.Emit.ModuleBuilder.GetTypeTokenWorkerNoLock(Type type, Boolean getGenericDefinition)
   at System.Reflection.Emit.ModuleBuilder.GetTypeTokenInternal(Type type, Boolean getGenericDefinition)
   at System.Reflection.Emit.SignatureHelper.AddOneArgTypeHelperWorker(Type clsArgument, Boolean lastWasGenericInst)
   at System.Reflection.Emit.SignatureHelper.AddArgument(Type argument, Boolean pinned)
   at System.Reflection.Emit.ILGenerator.DeclareLocal(Type localType, Boolean pinned)
   at System.Reflection.Emit.ILGenerator.DeclareLocal(Type localType)
   at LambdaExpressions.Program.DefineMethod(MethodInfo method, TypeBuilder typeBuilder, LambdaExpressionDetails lambdaExpression) in D:\code\dotNet\BlogDemos\Reflection\LambdaExpressions\Program.cs:line 150
   at LambdaExpressions.Program.Main(String[] args) in D:\code\dotNet\BlogDemos\Reflection\LambdaExpressions\Program.cs:line 54
So, even though the nested type has compiled it cannot be used directly until the containing type has been fully defined, but the containing type is the one that I am still building. Thankfully, TypeBuilder inherits Type so I can simply use the TypeBuilder when declaring the local variable. The same occurs trying to use the Type.GetConstructor(), Type.GetMethod() or Type.GetField() methods on either the created type or on the TypeBuilder. However, when creating each of these members the appropriate method used returns a XXXBuilder object that inherits from its equivalent and can be used in its place:
  • ConstructorBuilder inherits ConstructorInfo
  • MethodBuilder inherits MethodInfo
  • FieldBuilder inherits FieldInfo
Hence, the DefineLambdaExpression method, stores these objects in a LambdaExpressionDetails object that can be passed on to the method where the call into this lambda expression will be made.

Making the call

The creation of the type and the initial definition of the methods of the ICalculator interface should be identical to that in previous posts; it is only the implementation that will differ. The four methods again look very similar, the only difference between them is the lambda expression that they are using:
var result = 0;
Evaluator.Evaluate(() => result = a * b);
return result;
As before, the test implementation and ILDASM shows how the calling method is constructed. The contents of each method can be distilled to the following (ignoring what has gone before in previous posts):
  • Declare a lamdba expression local variable
  • Declare the result local variable
  • Create a new instance of the lambda expression using the LambdaExpressionDetails.Constructor property
  • Save it to the lambda expression local variable
  • For each argument:
    • Push the lambda expression local variable onto the stack
    • Push the appropriate argument onto the stack
    • Store the value into the appropriate field on the lambda expression using the LambdaExpressionDetails.Method property
  • Load the lambda expression local variable
  • Load the function pointer to the method of the lambda expression using
  • Create a new instance of the CalcFunction delegate
  • Call the Evaluator.Evaluate() method
  • Load the result field
  • Return the result
So the IL becomes:
// Create the lambda expression object
il.Emit(OpCodes.Newobj, lambdaExpressionDetails.Constructor);
il.Emit(OpCodes.Stloc_0);

// Load all of the parameters into the lambda expression's fields
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stfld, 
   lambdaExpressionDetails.ParameterFields
      .SingleOrDefault(x => x.Name.Equals("a")));
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Stfld, 
   lambdaExpressionDetails.ParameterFields
      .SingleOrDefault(x => x.Name.Equals("b")));
il.Emit(OpCodes.Nop);

// Set up the return value
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stfld, lambdaExpressionDetails.ResultField);

// Make the call
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldftn, lambdaExpressionDetails.Method);
var constructorInfo = typeof(EvaluationDelegate)
   .GetConstructor(new[] { typeof(object), typeof(IntPtr) });
il.Emit(OpCodes.Newobj, constructorInfo);
var methodInfo = typeof(Evaluator)
   .GetMethod("Evaluate", new[] { typeof(EvaluationDelegate) });
il.Emit(OpCodes.Call, methodInfo);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldfld, lambdaExpressionDetails.ResultField);
il.Emit(OpCodes.Stloc_1);
il.Emit(OpCodes.Br_S, label);
il.MarkLabel(label);
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Ret);

Finally...

Today, I have shown that lambda expressions are really nothing special under the hood, from what I can tell there are no features of MSIL that make lambda expressions possible, they are made possible through features of the languages and the compilers that produce MSIL. By investigating the structure of the generated MSIL for this one case I hope to have demonstrated the principles behind dynamically generating your own lambda expressions for a variety of other cases. That is it for my investigation into MSIL and Refleciton.Emit. My guiding principles throughout these tutorials has been to make an example of what I wish to produce then use ILDASM to break the compiled assembly down into MSIL to gain an understanding of what is produced. I hope that these tutorials will prove useful to anyone trying to make use of Reflection.Emit but I hope that when I have achieved my ultimate goal (a generic WCF Service Client factory) will prove valuable and that will be the topic of my next blog post.

Tags:

blog comments powered by Disqus