Generar dinámicamente una acción.

.net c# expression-trees

Pregunta

Estoy tratando de descubrir cómo crear dinámicamente una Acción en tiempo de ejecución pero quedarme corto.

Digamos que quiero llamar a un método y pasar una Acción creada dinámicamente para poder hacer un seguimiento de si la Acción ha sido invocada, etc. (por cualquier motivo).

void DoSomething(Action<string> action);

Ese es el método que invocaré y quiero construir de alguna manera dinámicamente una Acción que satisfaga el parámetro.

Sé que podría crear uno usando la new Action<string>((s) => { });

Pero para este caso, no sé en tiempo de compilación la firma de la Acción y todo lo que quiero es una Acción supergénica que me avisará si se ha invocado.

Esto es parte de un sistema de comunicación para un proyecto y quiero poder admitir que se puedan utilizar las acciones (piense en una devolución de llamada de OnCompleted).

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

Quiero poder generar una representación, disparar a través del cable, crear una Acción dinámicamente en el servidor, invocar el método en el objeto y pasar mi acción dinámica, enviar el resultado al cliente e invocar la acción real allí. .

Una pequeña aclaración:

Lado del cliente:

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

Debajo:

  1. Descubre la firma de la Acción.
  2. Construye un objeto de representación interna (lo suficientemente fácil)
  3. Envía eso por el cable al servidor

Lado del servidor:

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

Luego, de vuelta en el lado del cliente, los parámetros pasados ​​a la acción generada dinámicamente en el servidor, la acción real simplemente se invoca con ellos.

Respuesta aceptada

De acuerdo,

Así que parece que resolví el problema hasta cierto punto, es desagradable y probablemente bastante lejos de cómo debería verse.

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

Básicamente, esto construye una lista para contener todos los parámetros de Acción que el método establece en:

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

Los parámetros de la expresión se crean utilizando los argumentos de tipo de la acción y se crean varias expresiones de establecimiento para agregar ese parámetro en particular a la lista de valores.

Cuando se ejecuta, la expresión asigna la lista de valores como una constante a una variable que tiene, las expresiones de establecimiento luego agregan sus valores a la lista y la expresión regresa.

El resultado neto es que cualquier Acción puede tener una acción generada dinámicamente y los resultados simplemente se incluyen en una lista, la lista podría enviarse a través del cable del servidor al cliente y los valores se usarían en el cliente como argumentos para los resultados reales. acción.


Respuesta popular

Aquí hay un ejemplo de cómo puedes construir un árbol de expresiones de este 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");

Como alternativa, puede usar Reflection.Emit para generar un delegado en tiempo de ejecución.

Por ejemplo:

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

Esto generará el siguiente delegado:

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


Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow