views:

528

answers:

1

Hello,

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?

+5  A: 

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();
}
Marc Gravell
Thanks for your reply. You are right about Func<...> - I have to limit the accepted arguments to four.I want to decouple my code from the Resolve()-Method (which is provided by an IoC-container). Instead I want to be able to create little factories (passed over as Func<> arguments) which enable an object to create more instances of one type - by using indirectly the IoC-container.
tanascius
Thanks ... I am new to Expressions. Can you give me a hint how to make Resolve() an instance method with the signature "object Resolve(Type, params object)"? I am still stuck :/
tanascius
Sorry, I had to leave for a train; I can elaborate on this area to high degree - but first I'd repeat my question: what is the scenario here? I could approach this in at least 3 ways (and none of them is Reflection.Emit)... if it is an *instance* method... what is it an instance *of*? Perhaps you could show a bit more semi-complete. And if you're not sure of things yet, feel free to unmark this as the answer - I won't be offended. Only mark it complete when you're happy that the question is resolved (I'm not sure that it is...)
Marc Gravell
I accepted your answer, because I am sure it provides useful informations to me :) I just have to find out how to call an instance method "object Resolve(Type, params object)" with Expressions.I edited my post - hopefully it clarifies what I want to do ...
tanascius
(edited my question)
tanascius