Dynamically generate an Action

.net c# expression-trees

Question

I'm trying to work out how to dynamically create an Action at runtime but coming up short.

Let's say I want to call a method and pass in a dynamically created Action so I can track whether the Action has been invoked etc (for whatever reason).

void DoSomething(Action<string> action);

That's the method I'm going to invoke and I want to somehow dynamically build an Action that'll satisfy the parameter.

I know I could just build one using new Action<string>((s) => { });

But for this case I don't know at compile-time the signature of the Action and all I want is a super-generic Action that'll let me know if it's been invoked.

This is part of a communication system for a project and I want to be able to support Actions being usable (think an OnCompleted callback).

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

I want to be able to generate a representation, shoot that over the wire, create an Action dynamically on the server, invoke the method on the object and pass in my dynamic action, send the result back to the client and invoke the actual action there.

A little clarification:

Client Side:

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

Underneath:

  1. Discover the signature of the Action
  2. Build an internal representation object (easy enough)
  3. Send that over the wire to the server

Server Side:

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

Then back on the client side the parameters passed into the dynamically generated action on the server, the real action just gets invoked with those.

Accepted Answer

Ok,

So I've appeared to solve the problem to a degree, it's nasty and probably quite a way from how it should look.

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

Basically, this builds a list to hold all the Action parameters the method sets in:

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

The parameters of the expression are built using the type arguments of the action and a number of setter expressions are built to add that particular parameter into the value list.

When run the expression assigns the values list as a constant to a variable it has, the setter expressions then add their values into the list and the expression returns.

The net result is that any Action can have an action dynamically generated and the results just chucked into a list, the list could then be sent across the wire from server to client and the values would be used on the client as the arguments to the actual action.


Popular Answer

Here's an example of how you could build such an expression tree:

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

As an alternative you could use Reflection.Emit to generate a delegate at runtime.

For example:

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

This will generate the following delegate:

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


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why