在IronPython中我试图用C#中的不同数量的参数调用PythonFunction。例如;
我想这样做:
def foo(a, b):
print a, b
def bar(a, b, c = None):
print a, b, c
p = App.DynamicEvent()
p.addHandler(foo)
p.addHandler(bar)
p.invoke("Not", "Working")
其中addHandler
接受一个参数,并以某种方式将其存储在要调用的方法列表中,invoke
具有如下签名:
public virtual void invoke(params object[] tArgs)
因为我想避免使其特定于PythonEngine
(以及engine.Operations.Invoke()
),我尝试了几种存储和实现这些事情的方式作为代表,但我认为我的问题的关键是因为我不知道如何存储某种与MulticastDelegate
兼容的PythonFunction
基类型?
也许我想实现自己的DynamicInvoke
方法?任何想法和经验都将不胜感激!
想要这样做的原因是我希望通过C#透明地将密封的Javascript引擎发出的调用映射到IronPython中。即在Javascript调用中:Client.doThing("something", 4, {"key:"value"})
并使用以下命令在python中处理它:
def doThing(s, i, d):
pass
使用以下动态事件绑定:
doThingEvent = App.DynamicEvent()
doThingEvent.addHandler(doThing)
WebBrowser.handleMethod("doThing", doThingEvent);
答案 0 :(得分:3)
您可以将PythonFunction作为委托传递,例如通过强制转换为Action<...>
即。在Python中做类似的事情:
import sys
import clr
import System
from System import *
def foo(a, b):
print a, b
def bar(a, b, c = "N.A."):
print a, b, c
p = App.DynamicEvent()
p.AddHandler( Action[object,object](foo) )
p.AddHandler( Action[object,object,object](bar) )
p.DynamicInvoke("Not", "Working")
通过这种方式,您的p
可以是MulticastDelegate
。
这显然意味着:
对于第一个问题,我认为你需要编写自己的“委托调用者”,例如:
//
// WARNING: very very rough code here !!
//
public class DynamicEvent
{
List<Delegate> delegates;
public DynamicEvent()
{
delegates = new List<Delegate>();
}
public void AddHandler(Delegate dlgt)
{
delegates.Add(dlgt);
}
public void Invoke(params object[] args)
{
foreach (var del in delegates)
{
var parameters = del.Method.GetParameters();
// check parameters number
if (args.Length != parameters.Length)
continue; // go to next param
// check parameters assignability
bool assignable = true;
for (int i = 0; i < args.Length; i++)
{
if (!parameters[i].ParameterType.IsInstanceOfType(args[i]))
{
assignable = false;
break; // stop looping on parameters
}
}
if (!assignable)
continue; // go to next param
// OK it seems compatible, let's invoke
del.DynamicInvoke(args);
}
}
}
<强> N.B:强>
检查委托 - 参数兼容性的部分是错误的,只是为了给你一个想法。
问题是我不知道如何在运行时检查对象args列表是否与委托签名兼容......也许我们应该检查IronPython源代码:)
答案 1 :(得分:0)
我的第一个想法是 digEmAll 建议,但我提出了一个更好的解决方案,不需要转换为System.Action
或任何调用时类型检查或分支。只需重载addHandler
即可获得PythonFunction
以及Delegate
,这意味着您可以使用原始代码:
def foo(a, b):
print a, b
def bar(a, b, c = None):
print a, b, c
p = DynamicEvent(engine)
p.addHandler(foo)
p.addHandler(bar)
p.invoke("a", "b")
p.invoke("a", "b", "c")
使用此C#代码:
public class DynamicEvent
{
private Dictionary<int, Action<object[]>> delegates = new Dictionary<int, Action<object[]>>();
public ScriptEngine Engine { get; set; }
public DynamicEvent(ScriptEngine engine)
{
Engine = engine;
}
public void addHandler(PythonFunction pythonFunction)
{
int args = (int) pythonFunction.func_code.co_nlocals;
delegates.Add(args, a => Engine.Operations.Invoke(pythonFunction, a));
}
public void addHandler(Delegate d)
{
int args = d.Method.GetParameters().Length;
delegates.Add(args, a => d.DynamicInvoke(a));
}
public void invoke(params object[] args)
{
Action<object[]> action;
if(!delegates.TryGetValue(args.Length, out action))
throw new ArgumentException("There is no handler that takes " + args.Length + " arguments!");
action(args);
}
}
请注意,您需要将engine
添加到脚本的范围内,以便在构造函数中使用它。
希望有所帮助!
注意:可以获得Delegate
并从PythonFunction
执行此操作:
Delegate target = pythonFunction.__code__.Target;
var result = target.DynamicInvoke(new object[] {pythonFunction, args});
但它比使用Engine.Operations.Invoke()
更依赖于平台,Delegate
签名与'常规'Delegate
不同。