2017-05-21 5 views
2

Je ces interface simple:Faire une expression lambda pour appeler une méthode d'une classe générique

public interface IQuery<TResult> { } 

public interface IQueryHandler<in TQuery, out TResult> 
    where TQuery : IQuery<TResult> { 
    TResult Handle(TQuery query); 
} 

Et il y a une mise en œuvre d'entre eux. J'essaye de créer une expression-arbre pour appeler la méthode Handle sur un gestionnaire indiqué. Je veux dire:

var query = new MyQuery(); // which MyQuery implements IQuery<int> 
object handler = _someServiceProvider 
    .Get<IQueryHandler<MyQuery, int>>(); 

En outre, il y a une MyQueryHandler:

public class MyQueryHandler : IQueryHandler<MyQuery, int> { 
    public int Handle(MyQuery query) { return 20; } 
} 

Maintenant, je suis en train de créer un Func<object, MyQuery, int> appeler comme ceci:

var func = GetMethod<MyQuery, int>(handler); 
var result = func(handler, query); 

Et voici mon GetMethod mise en œuvre:

private Func<object, TQuery, TResult> GetMethod<TQuery, TResult>(object obj) 
     where TQuery : IQuery<TResult> { 

     var methodInfo = obj.GetType().GetMethod(nameof(IQueryHandler<TQuery, TResult>.Handle)); 

     var insExp = Expression.Parameter(typeof(object), "ins"); 

     var inputExp = Expression.Parameter(typeof(TQuery), "query"); 

     var instanceExp = Expression.Variable(obj.GetType(), "instance"); 

     var assExp = Expression.Assign(instanceExp, Expression.Convert(insExp, obj.GetType())); 

     var castExp = Expression.Convert(inputExp, methodInfo.GetParameters()[0].ParameterType); 

     var callExp = Expression.Call(instanceExp, methodInfo, castExp); 

     var blockExp = Expression.Block(new Expression[] { 
      insExp, 
      inputExp, 
      instanceExp, 
      assExp, 
      castExp, 
      callExp 
     }); 

     var func = 
      Expression.Lambda<Func<object, TQuery, TResult>>(
       blockExp, 
       insExp, 
       inputExp).Compile(); 
     return func; 
    } 

Mais, quand je tente de compiler Lambda, je reçois cette erreur:

An exception of type 'System.InvalidOperationException' occurred in System.Core.dll but was not handled in user code

Additional information: variable 'instance' of type 'Namespace.MyQueryHandler' referenced from scope '', but it is not defined

Où suis-je tort? Qu'est-ce que j'ai manqué? Avez-vous une idée? Merci d'avance.

+0

pourquoi avez-vous besoin de fusionner l'expression des arbres à cela? il semble que trop de complication – MBoros

Répondre

1

Pour autant que je peux dire, vous essayez d'écrire cette fonction:

TResult f(object ins, TQuery query) 
{ 
    var instance = (MyQueryHandler)ins; 
    return instance.Handle(query); 
} 

Pour ce faire, en utilisant des arbres d'expression, vous devez déclarer la variable dans votre Expression.Block, mais seulement préciser les deux énoncés ci-dessus, ne sont pas tous les sous-expressions:

var blockExp = Expression.Block(new[] { instanceExp }, new Expression[] { 
    assExp, 
    callExp 
}); 

mais une option plus simple serait d'écrire la fonction suivante à la place:

TResult f(object ins, TQuery query) 
{ 
    return ((MyQueryHandler)ins).Handle(query); 
} 

Cela ressemblerait à ceci:

var callExp = Expression.Call(
    Expression.Convert(insExp, obj.GetType()), methodInfo, castExp); 

var func = 
    Expression.Lambda<Func<object, TQuery, TResult>>(
     callExp, 
     insExp, 
     inputExp).Compile(); 
+0

Merci beaucoup. J'ai compris. Mais question: qu'est-ce que Dump() 'ici et que fait-il? Aussi, ceci est un code rouge): –

+1

@Javad_Amiry Dump est probable parce que svick utilisait [LinqPad] (https://www.linqpad.net/) pour déboguer. Retirez-le. – DavidG

+0

@DavidG Merci bro;) acclamations –

1

Ce n'est pas vraiment clair ce que vous essayez de faire avec votre expression dans GetMethod, donc je ne vais pas l'utiliser et écrire complètement à partir de zéro.

Si vous voulez passer à la fois gestionnaire et requête à votre méthode - vous n'avez pas besoin de passer à toutes les instances GetMethod:

private static Func<object, TQuery, TResult> GetMethod<TQuery, TResult>() 
    where TQuery : IQuery<TResult> { 
    // "query" paramter 
    var query = Expression.Parameter(typeof(TQuery), "query"); 
    // "handler" parameter 
    var handler = Expression.Parameter(typeof(object), "handler"); 
    // convert your "object" parameter to handler type (not type safe of course) 
    // ((IQueryHandler<TQuery, TResult>) handler).Handle(query) 
    var body = Expression.Call(Expression.Convert(handler, typeof(IQueryHandler<TQuery, TResult>)), "Handle", new Type[0], query); 
    //(handler, query) => ((IQueryHandler<TQuery, TResult>) handler).Handle(query); 
    return Expression.Lambda<Func<object, TQuery, TResult>>(body, handler, query).Compile(); 
} 

object handler = new MyQueryHandler(); 
var func = GetMethod<MyQuery, int>(); 
var result = func(handler, query); 

Si vous faites passe handler par exemple à GetMethod - vous n » pas besoin de passer plus tard cette même instance de nouveau à votre créé func - vous pouvez réutiliser la même instance comme celui-ci (en supposant que votre scénario correspond bien sûr):

private static Func<TQuery, TResult> GetMethod<TQuery, TResult>(object obj) 
    where TQuery : IQuery<TResult> { 
    // parameter 
    var query = Expression.Parameter(typeof(TQuery), "query"); 
    // note Expression.Constant here - you use the same instance for every call 
    var body = Expression.Call(Expression.Constant(obj), "Handle", new Type[0], query); 
    return Expression.Lambda<Func<TQuery, TResult>>(body, query).Compile(); 
} 

et de l'utiliser:

var query = new MyQuery(); // which MyQuery implements IQuery<int> 
object handler = new MyQueryHandler(); 
var func = GetMethod<MyQuery, int>(handler); 
var result = func(query); 
+0

Merci :) J'ai utilisé la réponse de svick. –

+1

Très bien, mais notez que vous n'avez pas besoin de passer l'instance de votre gestionnaire à la méthode juste pour obtenir la référence 'Handle', donc votre' GetMethod' peut être sans paramètres (mieux vaut ne pas passer de paramètres inutiles pour moi). – Evk

+0

D'accord. J'ai changé le paramètre d'une 'instance' à une' obj.GetType() '. Parce que, vous auriez besoin de savoir quel est le type d'instance actuelle et ne pouvez pas utiliser 'typeof (IQueryHandler )'. Mais merci encore. À votre santé. –