Как создать делегат или лямбда-выражение - PullRequest
3 голосов
/ 02 июня 2009

Я хочу создать метод, который возвращает Func <>. Внутри этого метода мне нужно создать делегат или лямбда-выражение, которое точно соответствует типу возвращаемого значения.

В целом это должно выглядеть так:

// 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" );

Внутри MissingMethod () он должен выглядеть следующим образом:

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 ) );
}

Я думаю, что единственный способ получить мой MissingMethod () - это использовать mirror.emit.

Знаете ли вы хорошие ресурсы или учебные пособия по испусканию лямбды или делегата?

Видите ли вы другое возможное решение этой проблемы?

РЕДАКТИРОВАТЬ:
Вот сценарий того, что я хочу достичь:

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" );
    // ...
  }
}

Я пытаюсь реализовать метод Container и Resolve (). Я знаю, что зарегистрирован тип Foo. И я знаю, что его конструктору нужна строка для вызова.

Когда мне нужно разрешить тип "ConsumerClass", я вижу, что он хочет ввести Func. Это не совсем то, что может предоставить мой контейнер, потому что обычно он предоставляет Foo единственные элементы доступа, например:

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

Но, тем не менее, контейнер должен иметь возможность предоставлять Func. Он имеет всю необходимую информацию.

Но теперь я застрял в создании этого связанного Func <,>. И помните, это тоже может быть Func <,,,>. Поэтому я ищу решение, которое может создать моих делегатов на лету. Они должны быть отлиты до точного связанного типа.

EDIT:
Я не уверен, как это лучше описать ... Я пытаюсь сделать что-то вроде this . Но я не хочу передавать цель. Вместо

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

мой делегат должен выглядеть как

delegate void object LateBoundMethod( object[] arguments );

и цель указана в качестве поля экземпляра. Принимая и улучшая решение Марка, я получаю:

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 );
}

Но это еще не все. Метод Resolve () больше не является статическим (потому что ему нужно два поля экземпляра) и не может быть вызван. Таким образом, проблема здесь

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

Вместо того, чтобы передавать нулевое значение в качестве первого аргумента, я думаю, что мне нужна ссылка на «это». Как мне это сделать?

1 Ответ

7 голосов
/ 02 июня 2009

Первая проблема в том, что Func<...> не существует - вам нужно кодировать Func<>, Func<,>, Func<,,>, Func<,,,> отдельно.

Теперь; Я понимаю код, но я не уверен, что сценарий, который вы пытаетесь решить ... вы можете уточнить? Есть, вероятно, лучшие варианты ...

Если это так сложно, как кажется, пользовательский Expression, вероятно, является наиболее подходящим вариантом (гораздо проще, чем Reflection.Emit).


Это работает, например ...

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();
}
...