17

I want to pass a list of strings from IronPython 2.6 for .NET 2.0 to a C# program (I'm using .NET 2.0 because I'm working with an api that runs off of DLLs built on 2.0). But I'm not sure how to cast it as it comes back from the ScriptEngine.

namespace test1
{
    class Program
    {
        static void Main(string[] args)
        {
            ScriptEngine engine = Python.CreateEngine();
            ScriptSource source = engine.CreateScriptSourceFromFile("C:\\File\\Path\\To\\my\\script.py");
            ScriptScope scope = engine.CreateScope();

            ObjectOperations op = engine.Operations;

            source.Execute(scope); // class object created
            object classObject = scope.GetVariable("MyClass"); // get the class object
            object instance = op.Invoke(classObject); // create the instance
            object method = op.GetMember(instance, "myMethod"); // get a method
            List<string> result = (List<string>)op.Invoke(method); // call the method and get result
            Console.WriteLine(result.ToString());
            Console.Read();
        }
    }
}

my python code has a class with a method that returns a python list of strings:

class MyClass(object):
    def myMethod(self):
        return ['a','list','of','strings']

I get this error:

Unable to cast object of type 'IronPython.Runtime.List' to type 'System.Collections.Generic.List`1[System.String]'.
BenjaminGolder
  • 1,573
  • 5
  • 19
  • 37

2 Answers2

19

IronPython.Runtime.List implements the following interfaces:

IList, ICollection, IList<object>, ICollection<object>, IEnumerable<object>, IEnumerable

so you can cast to one of this types an then turn into a List<string>.

List<string> result = ((IList<object>)op.Invoke(method)).Cast<string>().ToList();

BTW, maybe you are aware of it, but you can also use .NET types in IronPython e.g.:

from System.Collections.Generic import *

class MyClass(object):
    def myMethod(self):
        return List[str](['a','list','of','strings'])

here myMethod returns directly a List<string>


EDIT:

Given that you're using .net 2.0 (so no LINQ) you have two options (IMO):

1. Cast to IList<object> and use it:

IList<object> result = (IList<object>)op.Invoke(method);

PROs: no loop required, you will use the same object instance returned by the python script.
CONs: no type safety (you will be like in python, so you can add also a non-string to the list)

2. Convert to a List<string>/IList<string> :

IList<object> originalResult = (IList<object>)op.Invoke(method);
List<string> typeSafeResult = new List<string>();
foreach(object element in originalResult)
{
    typeSafeResult.Add((string)element);
}

PROs: type safe list (you can add only strings).
CONs: it requires a loop, and the converted list is a new instance (not the same returned by the script)

digEmAll
  • 56,430
  • 9
  • 115
  • 140
  • your suggestion makes sense, but when I try casting from IList, I get this error: `'System.Collections.Generic.IList' does not contain a definition for 'Cast'` I've probably missed something basic. – BenjaminGolder Mar 07 '11 at 07:43
  • In response to your suggestion about using .NET types, I was hoping that there were some simple casts for basic python types, such as lists, dictionaries, and tuples, that I wouldn't have to replace with .net types in my python script (it _is_ supposed to be built on .net after all). maybe there is a list of default casts for basic ironpython types somewhere – BenjaminGolder Mar 07 '11 at 07:48
  • @BenjaminGolder: do you have `using System.Linq;` among the `.cs` header lines ? (you're using .net 3.5 I hope...) – digEmAll Mar 07 '11 at 07:49
  • @digEmAll: no should I?, and I'm actually using 2.0, because I'm using an api that works from 2.0 dlls. Sorry if I should already know these things. I'm pretty new to .NET – BenjaminGolder Mar 07 '11 at 07:53
  • @BenjaminGolder: as I said, a python list is mapped as a `IList`, so if you want to manipulate the same object instance returned from python you must cast to that. Otherwise you can turn it into a `List` (using loops or LINQ `Cast<>`) but of course this means to create another instance. – digEmAll Mar 07 '11 at 07:57
  • @BenjaminGolder: well is not necessary to pass to .net 3.5, but you won't have LINQ (so no `Cast<>`). But, I read you need to stay in 2.0 for dependencies problem, so it's ok :) – digEmAll Mar 07 '11 at 08:04
  • @digEmAll: thank you for all your help and patience. I wish I could use 3.5, but I'm making a dll that will be used by an application that's running off of 2.0, and as a beginner, it seemed daunting to navigate the 2.0/3.5 interop divide along with the ironpython/c# interop divide. Is there a simple recasting method you might be able to suggest for 2.0? were loops the 2.0 solution? – BenjaminGolder Mar 07 '11 at 08:14
  • I actually do not get a `IronPython.Runtime.List` (I do have `using System.Linq;`). from a call like `Func> get_a_list = pyOps.GetMember>>(class1, "get_a_list");` where `get_a_list` is the Python code that returns a Python `[]` list. I get a `{IronPython.Runtime.ListGenericWrapper}`. How can I convert that? – Sven Feb 02 '15 at 14:05
2

You can use IList on the C# side and IronPython will automatically wrap the List object in a wrapper which does conversions to/from string when accessing the list.

Dino Viehland
  • 6,478
  • 20
  • 25