Genera dinamicamente un'azione

.net c# expression-trees

Domanda

Sto cercando di capire come creare dinamicamente un'azione in fase di runtime, ma in arrivo.

Diciamo che voglio chiamare un metodo e passare un'azione creata dinamicamente in modo da poter controllare se l'Azione è stata invocata ecc (per qualsiasi motivo).

void DoSomething(Action<string> action);

Questo è il metodo che invocherò e voglio creare in qualche modo in modo dinamico un'azione che soddisfi il parametro.

So che potrei semplicemente costruirne uno usando la new Action<string>((s) => { });

Ma per questo caso non so al momento della compilazione la firma dell'Azione e tutto ciò che voglio è un'Azione super-generica che mi faccia sapere se è stata invocata.

Questo fa parte di un sistema di comunicazione per un progetto e voglio essere in grado di supportare le azioni che sono utilizzabili (si pensi ad una richiamata OnCompleted).

Proxy.DoSomething((s) => Console.WriteLine("The server said: " + s);

Voglio essere in grado di generare una rappresentazione, sparare al di sopra del filo, creare un'azione in modo dinamico sul server, richiamare il metodo sull'oggetto e passare la mia azione dinamica, inviare il risultato al client e richiamare l'azione effettiva lì .

Un piccolo chiarimento:

Dalla parte del cliente:

var proxy = GetProxyObject(); // Comms proxy
proxy.DoSomething((reply) => Console.WriteLine("Server said: " + reply));

Sotto:

  1. Scopri la firma dell'Azione
  2. Costruisci un oggetto di rappresentazione interna (abbastanza facile)
  3. Mandalo via cavo al server

Lato server:

void ReceivedMessage(msg)
{
   var actParam = msg.Parameters[0]; // This is obviously just for demonstration
   var action = BuildActionWrapper(actParam);
   var result = target.InvokeMethod("DoSomething", action.UnderlyingAction);

   // Send result and Action result back to client
   ReplyToClient(...);
}

void DoSomething(Action<string> act)
{
   act("HELLO!");
}

Quindi, sul lato client, i parametri passati all'azione generata dinamicamente sul server, l'azione reale viene semplicemente invocata con quelli.

Risposta accettata

Ok,

Quindi mi è sembrato che risolvesse il problema fino a un certo punto, è brutto e probabilmente un bel modo da come dovrebbe apparire.

    var mi = this.GetType().GetMethod("DoSomething");
    var actArg = mi.GetParameters()[0];
    var args = actArg.ParameterType.GenericTypeArguments;

    var lt = Expression.Label();

    List<object> values = new List<object>();


    var valVar = Expression.Variable(typeof(List<object>), "vals");
    var para = args.Select(a => Expression.Parameter(a))
        .ToArray();

    var setters = new List<Expression>();

    foreach (var p in para)
    {
        setters.Add(Expression.Call(valVar,
            typeof(List<object>).GetMethod("Add", new[] { typeof(object) }), p));
    }

    var block = Expression.Block(
        variables: new ParameterExpression[]
        {
            valVar,
        },

        expressions: Enumerable.Concat(para,
        new Expression[]
        {
            Expression.Assign(valVar, Expression.Constant(values)),
        }.Concat(setters)
        .Concat(new Expression[]
        {
            Expression.Return(lt),
            Expression.Label(lt),
        })));
    var l = Expression.Lambda(block, para).Compile();
    mi.Invoke(this, new object[] { l });

Fondamentalmente, questo costruisce un elenco per contenere tutti i parametri di azione che il metodo imposta in:

public void DoSomething(Action<string> act)
{
    act("Hello");
}

I parametri dell'espressione sono costruiti usando gli argomenti type dell'azione e un numero di espressioni setter sono costruiti per aggiungere quel particolare parametro alla lista valori.

Quando si esegue l'espressione assegna l'elenco di valori come costante a una variabile che ha, le espressioni setter aggiungono i loro valori nella lista e l'espressione restituisce.

Il risultato è che qualsiasi azione può avere un'azione generata dinamicamente e i risultati appena inseriti in un elenco, l'elenco può quindi essere inviato attraverso il filo da server a client e i valori dovrebbero essere utilizzati sul client come argomenti per l'effettivo azione.


Risposta popolare

Ecco un esempio di come è possibile creare un albero di espressioni di questo tipo:

var mi = typeof(Console).GetMethod("WriteLine", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string) }, null);
var parameter = Expression.Parameter(typeof(string));
var body = Expression.Call(null, mi, new[] { parameter });
Expression<Action<string>> expression = Expression.Lambda<Action<string>>(body, new[] { parameter });

expression.Compile()("test");

In alternativa, è possibile utilizzare Reflection.Emit per generare un delegato in fase di runtime.

Per esempio:

var dynMethod = new DynamicMethod("", null, new[] { typeof(string) });
var il = dynMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
var mi = typeof(Console).GetMethod("WriteLine", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string) }, null);
il.Emit(OpCodes.Call, mi);
il.Emit(OpCodes.Ret);

var dynDelegate = (Action<string>)dynMethod.CreateDelegate(typeof(Action<string>));
dynDelegate("test");

Questo genererà il seguente delegato:

(string s) => Console.WriteLine(s)


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché