3

我正在尝试弄清楚如何在运行时动态创建一个动作,但结果很短。

假设我想调用一个方法并传入一个动态创建的动作,这样我就可以跟踪是否调用了动作等(无论出于何种原因)。

void DoSomething(Action<string> action);

这就是我要调用的方法,我想以某种方式动态构建一个满足参数的 Action。

我知道我可以使用new Action<string>((s) => { });

但是对于这种情况,我在编译时不知道 Action 的签名,我想要的只是一个超级通用的 Action,它会让我知道它是否被调用。

这是项目通信系统的一部分,我希望能够支持可用的操作(想想 OnCompleted 回调)。

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

我希望能够生成一个表示,通过网络拍摄,在服务器上动态创建一个动作,调用对象上的方法并传入我的动态动作,将结果发送回客户端并在那里调用实际动作.

一点澄清:

客户端:

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

下:

  1. 发现动作的签名
  2. 构建一个内部表示对象(很简单)
  3. 通过网络将其发送到服务器

服务器端:

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

然后回到客户端,将参数传递给服务器上动态生成的动作,真正的动作只是被这些参数调用。

4

3 回答 3

1

这是一个如何构建这样一个表达式树的示例:

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

作为替代方案,您可以使用Reflection.Emit它在运行时生成委托。

例如:

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

这将生成以下委托:

(string s) => Console.WriteLine(s)
于 2013-02-18T09:01:46.793 回答
0

DoSomething签名转换为以下内容;

void DoSomething<T>(Action<T> action, T parameter)
{
    action(parameter);
}

并称之为;

Proxy.DoSomething<string>((q) => Console.WriteLine("The server said: " + q, "some string");
于 2013-02-18T08:59:09.253 回答
0

行,

所以我似乎在一定程度上解决了这个问题,这很糟糕,而且可能与它的外观相差甚远。

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

基本上,这会构建一个列表来保存方法设置的所有 Action 参数:

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

表达式的参数是使用动作的类型参数构建的,并且构建了许多 setter 表达式以将该特定参数添加到值列表中。

运行时,表达式将值列表作为常量分配给它拥有的变量,然后 setter 表达式将它们的值添加到列表中,表达式返回。

最终结果是任何 Action 都可以动态生成一个动作,并将结果放入一个列表中,然后该列表可以通过线路从服务器发送到客户端,并且这些值将在客户端上用作实际的参数行动。

于 2013-02-18T10:54:35.337 回答