How do I convert an Expression into a MethodBuilder instance method?
Asked Answered
A

1

9

I'm trying to generate a type at run time via a TypeBuilder. I'm generating the instance methods of the type using a MethodBuilder, however I don't want to generate the il via IlGenerator.Emit; instead I'd like to create an expression that represents the method, so can I convert it into an MethodBuilder for an instance method.

Is this possible? If so, how do I convert an Expression into a MethodBuilder instance method?

Aarika answered 1/2, 2014 at 16:45 Comment(0)
A
15

Quick Summary

Yes, you can, but you have to do some extra work. Jump down to the code snippet for the code to do so.

Long Answer

The Problem

Not directly, no. As noted in the SO Question: LambdaExpression CompileToMethod, while LambdaExpression.CompileToMethod in .NET 4.0 does take a MethodBuilder, it can only represent a static method.

A Partial Solution

So then, you need to work around this limitation by first creating a static method reference, and then create an instance method which calls that static method. If your expression does not have any "live objects" in it (i.e., you use an existing object reference when you were creating the Expression), then it's pretty straightforward to create a static method and then create an instance method that calls the static method. However, if you have a "live object" in your expression, CompileToMethod will inform you that it cannot use the Expression because you have a live object in your expression.

A Full Solution

Instead of creating a static method, you can instead add a delegate field to your generated type, and then from your instance method, call the delegate field and forward the method arguments to the delegate.

Code

Assuming a TypeBuilder called _typeBuilder, a MethodBuilder called methodBuilder, and a Delegate to forward to named delegateToInvoke:

// create a field to hold the dynamic delegate
var fieldBuilder = _typeBuilder.DefineField(
  "<>delegate_field",
  delegateToInvoke.GetType(),
  FieldAttributes.Private);

// remember to set it later when we create a new instance
_fieldsToSet.Add(new KeyValuePair<FieldInfo, object>(fieldBuilder, delegateToInvoke));

var il = methodBuilder.GetILGenerator();

// push the delegate onto the stack
il.Emit(OpCodes.Ldarg_0);
// by loading the field
il.Emit(OpCodes.Ldfld, fieldBuilder);

// if the delegate has a target, that means the first argument is really a pointer to a "this"
// object/closure, and we don't want to forward it. Thus, we skip it and continue as if it
// wasn't there.
if (delegateToInvoke.Target != null)
{
  parameters = parameters.Skip(1).ToArray();
}

// push each argument onto the stack (thus "forwarding" the arguments to the delegate).
for (int i = 0; i < parameters.Length; i++)
{
  il.Emit(OpCodes.Ldarg, i + 1);
}

// call the delegate and return
il.Emit(OpCodes.Callvirt, delegateToInvoke.GetType().GetMethod("Invoke"));
il.Emit(OpCodes.Ret);

When you create a new instance, be sure to set the field before using the instance:

generatedType.GetField("<>delegate_field", BindingFlags.NonPublic | BindingFlags.Instance)
             .SetValue(instance, delegateToInvoke);
Aarika answered 1/2, 2014 at 16:45 Comment(1)
This deserves to be upvoted; not because the solution is pretty but because it may be one of your only options. The only other possible solution I've found is noted here and makes awkward use of interface (although avoids IL emission): justinmchase.com/2009/01/08/…Wavemeter

© 2022 - 2024 — McMap. All rights reserved.