IronPython函数具有可变数量的参数作为委托

时间:2010-10-02 13:25:19

标签: c# delegates ironpython params

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

2 个答案:

答案 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。 这显然意味着:

  1. 所有传递的代表必须具有相同的签名
  2. 你可以传递最多16个参数的Python函数(因为Action支持最多16个参数)
  3. 对于第一个问题,我认为你需要编写自己的“委托调用者”,例如:

    //
    // 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不同。