pythonc#ironpythondynamic-language-runtime

How to pass a dynamic list from IronPython to C#


I want to get a python app to pass a generic list to my C# code. I've created a demo app that duplicates the problem I'm seeing.

I have this python code (Python 2.7), MainApp.py, that calls a C# DLL (.NET 4.7):

import clr, sys

sys.path.append(r"C:\PathToMyProject\ClassLibrary1\bin\Debug")
clr.AddReference(r"C:\PathToMyProject\ClassLibrary1\bin\Debug\ClassLibrary1.dll")

from ClassLibrary1 import Class1

class Person:
    def __init__(self, Name):
        self.Name = Name


myclass = Class1()

nameList = []
nameList.append(Person("Joe"))
nameList.append(Person("Mary"))
nameList.append(Person("Chris"))

result = myclass.SayHello(nameList)

print(result)

Notice that I have a list of Person objects, nameList, that I'm trying to pass. Here's the C# code:

using System.Collections.Generic;

namespace ClassLibrary1
{
    public class Class1
    {
        public string SayHello(List<dynamic> names)
        {
            return $"Hello, {names.Count} people!";
        }
    }
}

The SayHello method accepts a parameter of List<dynamic>. However, I receive the following error when running >python MainApp.py:

Traceback (most recent call last): File ".\MainApp.py", line 20, in result = myclass.SayHello(nameList) TypeError: No method matches given arguments for SayHello: (<type 'list'>)


Solution

  • I solved it with the following code:

    MainApp.py:

    import clr, sys
    
    sys.path.append(r"C:\Projects\temp\ClassLibrary1\bin\Debug")
    clr.AddReference(r"C:\Projects\temp\ClassLibrary1\bin\Debug\ClassLibrary1.dll")
    
    from ClassLibrary1 import Class1
    from ClassLibrary1 import Person
    
    myclass = Class1()
    
    nameList = []
    nameList.append(Person("Joe"))
    nameList.append(Person("Mary"))
    nameList.append(Person("Chris"))
    
    result = myclass.SayHello(nameList)
    
    print(result)
    

    Class1.cs:

    namespace ClassLibrary1
    {
        public class Class1
        {
            public string SayHello(dynamic[] names)
            {
                foreach (var item in names)
                {
                    System.Console.WriteLine(item.Name);
                }
    
                return $"Hello, {names.Length} people!";
            }
        }
    
        public class Person
        {
            public Person(string name)
            {
                Name = name;
            }
    
            public string Name { get; set; }
        }
    }
    

    The first thing I did was change the SayHello parameter type from List<dynamic> to dynamic[].

    public string SayHello(dynamic[] names)
    

    That fixed the type error, but I began receiving a new message:

    Unhandled Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
    at Python.Runtime.Runtime.PyObject_GetAttrString(IntPtr pointer, String name) at Python.Runtime.PyObject.GetAttr(String name) at Python.Runtime.PyObject.TryGetMember(GetMemberBinder binder, Object& result) at CallSite.Target(Closure , CallSite , Object ) at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0) at ClassLibrary1.Class1.SayHello(Object[] names) in C:\Projects\csharp-nine-cookbook\csharp-nine-cookbook\CSharp9Cookbook\ClassLibrary1\Class1.cs:line 11 --- End of inner exception stack trace --- at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
    at Python.Runtime.MethodBinder.Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo) at Python.Runtime.MethodObject.Invoke(IntPtr target, IntPtr args, IntPtr kw, MethodBase info) at Python.Runtime.MethodBinding.tp_call(IntPtr ob, IntPtr args, IntPtr kw)

    Apparently IronPython didn't let me pass custom types from python to C#. To fix this, I added a Person class to the C# code:

    public class Person
    {
        public Person(string name)
        {
            Name = name;
        }
    
        public string Name { get; set; }
    }
    

    Then I removed the custom Person class from the python code and referenced the one in the C# code:

    from ClassLibrary1 import Person
    

    Everything worked fine after that.