Howto emit a delegate or lambda expression

2019-02-11 01:18发布

问题:

I want to emit a method that returns a Func<>. Inside this method I have to create a delegate or a lambda expression which exactly serves the return type.

Altogether it should look like this:

// I have a resolve method that will be called inside my missing method
// This is it's signature:
object Resolve( params object[] args);

// This is how I use it:
var barFactory = ( Func<IBar> )MissingMethod( typeof( IBar ) );
var bar = barFactory.Invoke();

// or - with one string argument:
var fooFactory = ( Func<string, IFoo> )MissingMethod( typeof( IFoo ), typeof( string ) );
var foo = fooFactory.Invoke( "argument for foo" );

Inside the MissingMethod() it should look like:

object MissingMethod( Type returnType, params Type[] argTypes )
{
  // Create the type of Func<> based on the passed returnType and the argTypes
  var funcType = typeof(Func<,...,>).MakeGenericType( ... )

  // Here I have to use the Resolve() method and cast the lambda to the correct type
  return (cast to funcType)( (arg1, arg2) => Resolve( arg1, arg2 ) );
}

I think that the only way to get my MissingMethod() is, to use reflection.emit.

Do you know good resources or tutorials about emitting a lambda or a delegate?

Do you see another possible solution for this problem?

EDIT:
Here is a scenario of what I want to achive:

static void Main()
{
  var container = new Container();
  container.Register<Foo>();
  container.Register<ConsumerClass>();

  var consumerClass = Container.Resolve<ConsumerClass>();
}

class Foo()
{
  public Foo( string argument ) {}
}

class ConsumerClass
{
  public ConsumerClass( [Inject] Func<string, Foo> factory )
  {
    var foo1 = factory.Invoke( "first foo" );
    var foo2 = factory.Invoke( "another foo" );
    // ...
  }
}

I am trying to implement the Container and the Resolve() method. I know that there is a "Foo" type registered. And I know that its constructor needs a string to be invoked.

When I have to resolve the type "ConsumerClass" I see that it wants to get a Func injected. That is not exactly what my container can provide, because normally it provides single instaces to Foo like this:

Container.Resolve<Foo>( "argument" );

But nevertheless the container should be able to provide a Func, too. It has all informations needed.

But now I am stuck in creating this bound Func<,>. And remeber it could be a Func<,,,>, too. So I am looking for a solution that can create my this delegates on the fly. They have to be castable to the exact bound type.

EDIT:
I am not sure how to describe it better ... I am trying to do something like this. But I do not want to pass a target. Instead of

delegate void object LateBoundMethod( object target, object[] arguments );

my delegate should look like

delegate void object LateBoundMethod( object[] arguments );

and the target is provided as an instance field. By taking and 'improving' the solution of Marc I get:

private Delegate CreateDelegate( Type returnType, Type[] parameterTypes )
{
  m_Type = returnType;

  var i = 0;
  var param = Array.ConvertAll( parameterTypes, arg => Expression.Parameter( arg, "arg" + i++ ) );
  var asObj = Array.ConvertAll( param, p => Expression.Convert( p, typeof( object ) ) );
  var argsArray = Expression.NewArrayInit( typeof( object ), asObj );

  var callEx = Expression.Call( null, typeof( FuncFactory ).GetMethod( "Resolve" ), argsArray );
  var body = Expression.Convert( callEx, returnType );

  var ret = Expression.Lambda( body, param ).Compile();
  return ret;
}

private readonly Container m_Container;
private Type m_Type;

public object Resolve( params object[] args )
{
  return m_Container.Resolve( m_Type, args );
}

But this is incomplete. The Resolve()-method is not static anymore (because it needs two instance fields) and cannot be called. So the problem here is

var callEx = Expression.Call( null, typeof( FuncFactory ).GetMethod( "Resolve" ), argsArray );

Instead of passing null as the first argument I think I need a reference to 'this'. How do I do that?

回答1:

The first problem is that Func<...> doesn't exist - you'd need to code to Func<>, Func<,>, Func<,,>, Func<,,,> separately.

Now; I understand the code, but I'm not sure what the scenario is that you are trying to solve... can you clarify? There are probably better options...

If it is as complex as it sounds, a custom Expression is probably the most appropriate option (far simpler than Reflection.Emit).


This works, for example...

static Delegate MissingFunc(Type result, params Type[] args)
{
    int i = 0;
    var param = Array.ConvertAll(args,
        arg => Expression.Parameter(arg, "arg" + i++));
    var asObj = Array.ConvertAll(param,
        p => Expression.Convert(p, typeof(object)));
    var argsArray = Expression.NewArrayInit(typeof(object), asObj);
    var body = Expression.Convert(Expression.Call(
                null, typeof(Program).GetMethod("Resolve"),
                argsArray), result);
    return Expression.Lambda(body, param).Compile();
}
static void Main()
{
    var func2 = MissingFunc(typeof(string), typeof(int), typeof(float));
}
public static object Resolve( params object[] args) {
    throw new NotImplementedException();
}