我正在尝试研究如何在运行时动态创建一个Action,但是时间很短。
假设我想调用一个方法并传入一个动态创建的Action,这样我就可以跟踪Action是否被调用等(无论出于何种原因)。
void DoSomething(Action<string> action);
这是我要调用的方法,我想以某种方式动态构建一个满足参数的Action。
我知道我可以使用new Action<string>((s) => { });
但是对于这种情况,我不知道在编译时Action的签名和我想要的是一个超级通用的Action,它会告诉我它是否已被调用。
这是项目通信系统的一部分,我希望能够支持可用的操作(想想一个OnCompleted回调)。
Proxy.DoSomething((s) => Console.WriteLine("The server said: " + s);
我希望能够生成一个表示,通过线路进行拍摄,在服务器上动态创建Action,调用对象上的方法并传入我的动态操作,将结果发送回客户端并调用那里的实际行动。
稍作澄清:
客户端:
var proxy = GetProxyObject(); // Comms proxy
proxy.DoSomething((reply) => Console.WriteLine("Server said: " + reply));
下边:
服务器端:
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!");
}
然后在客户端返回传递到服务器上动态生成的操作的参数,实际操作就会被调用。
答案 0 :(得分: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)
答案 1 :(得分:0)
将DoSomething
签名转换为以下内容;
void DoSomething<T>(Action<T> action, T parameter)
{
action(parameter);
}
并称之为;
Proxy.DoSomething<string>((q) => Console.WriteLine("The server said: " + q, "some string");
答案 2 :(得分: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都可以动态生成一个动作,结果只是放入一个列表中,然后可以通过线路将列表从服务器发送到客户端,并将值作为参数在客户端上使用实际行动。