diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c999d668..c22ac2f16 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
- Added automatic NuGet package generation in appveyor and local builds
- Added function that sets Py_NoSiteFlag to 1.
- Added support for Jetson Nano.
+- Added support for methodbinding to IEnumerable, ICollection, IList, Task, Action, Func
### Changed
diff --git a/src/runtime/ListWrapper.cs b/src/runtime/ListWrapper.cs
new file mode 100644
index 000000000..5173be4b0
--- /dev/null
+++ b/src/runtime/ListWrapper.cs
@@ -0,0 +1,201 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Python.Runtime {
+ ///
+ /// Implements IEnumerable for any python iterable.
+ ///
+ internal class IterableWrapper : IEnumerable {
+ private IntPtr iterObject;
+
+ public IterableWrapper(IntPtr value) {
+ iterObject = Runtime.PyObject_GetIter(value);
+ if (iterObject == IntPtr.Zero)
+ Exceptions.RaiseTypeError("not an iterator");
+ Runtime.XIncref(iterObject);
+ }
+ ~IterableWrapper() {
+ Runtime.XDecref(iterObject);
+ }
+
+ public IEnumerator GetEnumerator() {
+ return GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() {
+ IntPtr item;
+ while ((item = Runtime.PyIter_Next(iterObject)) != IntPtr.Zero) {
+ object obj = null;
+ if (!Converter.ToManaged(item, typeof(T), out obj, true)) {
+ Runtime.XDecref(item);
+ Runtime.XDecref(iterObject);
+ Exceptions.RaiseTypeError("wrong type in sequence");
+ }
+
+ Runtime.XDecref(item);
+ yield return obj;
+ }
+ }
+ }
+
+ ///
+ /// Implements IList for any python sequence.
+ /// Some methods/properties are only available on certaintypes of sequences, like list
+ ///
+ internal class ListWrapper : IterableWrapper, IList
+ {
+ private IntPtr seq;
+ public ListWrapper(IntPtr value) : base(value)
+ {
+ this.seq = value;
+ Runtime.XIncref(value);
+ bool IsSeqObj = Runtime.PySequence_Check(value);
+ if (!IsSeqObj)
+ Exceptions.RaiseTypeError("not a sequence");
+
+ }
+ ~ListWrapper()
+ {
+ Runtime.XDecref(seq);
+ }
+ public T this[int index]
+ {
+ get
+ {
+ IntPtr item = Runtime.PySequence_GetItem(seq, index);
+ object obj;
+
+ if (!Converter.ToManaged(item, typeof(T), out obj, true)) {
+ Runtime.XDecref(item);
+ Exceptions.RaiseTypeError("wrong type in sequence");
+ }
+
+ return (T)obj;
+ }
+ set
+ {
+ IntPtr pyItem = Converter.ToPython(value, typeof(T));
+ if (pyItem == IntPtr.Zero)
+ throw new Exception("failed to set item");
+
+ var result = Runtime.PySequence_SetItem(seq, index, pyItem);
+ Runtime.XDecref(pyItem);
+ if (result == -1)
+ throw new Exception("failed to set item");
+ }
+ }
+
+ public int Count
+ {
+ get
+ {
+ var len = Runtime.PySequence_Size(seq);
+ return (int)len;
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return Runtime.PyTuple_Check(seq); //python tuples are immutable
+ }
+
+ }
+
+ public void Add(T item) {
+ if (IsReadOnly)
+ throw new NotImplementedException();
+
+ //only implemented if this is a list!
+ if (!Runtime.PyList_Check(seq))
+ throw new NotImplementedException();
+
+ IntPtr pyItem = Converter.ToPython(item, typeof(T));
+ if (pyItem == IntPtr.Zero)
+ throw new Exception("failed to add item");
+
+ var result = Runtime.PyList_Append(seq, pyItem);
+ Runtime.XDecref(pyItem);
+ if (result == -1)
+ throw new Exception("failed to add item");
+ }
+
+ public void Clear() {
+ if (IsReadOnly)
+ throw new NotImplementedException();
+ var result = Runtime.PySequence_DelSlice(seq, 0, Count);
+ if (result == -1)
+ throw new Exception("failed to clear sequence");
+ }
+
+ public bool Contains(T item)
+ {
+ //not sure if IEquatable is implemented and this will work!
+ foreach (var element in this)
+ if (element.Equals(item)) return true;
+
+ return false;
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ for (int index = 0; index < Count; index++)
+ {
+ array[index + arrayIndex] = this[index];
+ }
+ }
+
+ public int IndexOf(T item) {
+ var index = 0;
+ foreach (var element in this) {
+ if (element.Equals(item)) return index;
+ index++;
+ }
+
+ return -1;
+ }
+
+ public void Insert(int index, T item)
+ {
+ if (IsReadOnly)
+ throw new NotImplementedException();
+
+ //only implemented if this is a list!
+ if (!Runtime.PyList_Check(seq))
+ throw new NotImplementedException();
+
+ IntPtr pyItem = Converter.ToPython(item, typeof(T));
+ if (pyItem == IntPtr.Zero)
+ throw new Exception("failed to insert item");
+
+ var result = Runtime.PyList_Insert(seq, index, pyItem);
+ Runtime.XDecref(pyItem);
+ if (result == -1)
+ throw new Exception("failed to insert item");
+ }
+
+ public bool InternalRemoveAt(int index)
+ {
+ if (IsReadOnly)
+ throw new NotImplementedException();
+ if (index >= Count || index < 0)
+ throw new IndexOutOfRangeException();
+
+ return Runtime.PySequence_DelItem(seq, index) != 0;
+ }
+
+ public bool Remove(T item)
+ {
+ return InternalRemoveAt(IndexOf(item));
+ }
+
+ public void RemoveAt(int index)
+ {
+ InternalRemoveAt(index);
+ }
+ }
+}
diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs
index e7e047419..c96270b81 100644
--- a/src/runtime/converter.cs
+++ b/src/runtime/converter.cs
@@ -6,6 +6,7 @@
using System.Runtime.InteropServices;
using System.Security;
using System.ComponentModel;
+using System.Linq;
namespace Python.Runtime
{
@@ -268,7 +269,6 @@ internal static IntPtr ToPythonImplicit(object value)
return ToPython(value, objectType);
}
-
///
/// Return a managed object for the given Python object, taking funny
/// byref types into account.
@@ -343,6 +343,23 @@ internal static bool ToManagedValue(IntPtr value, Type obType,
return ToArray(value, obType, out result, setError);
}
+ if (obType.IsGenericType)
+ {
+ if (obType.GetGenericTypeDefinition() == typeof(IEnumerable<>) ||
+ obType.GetGenericTypeDefinition() == typeof(ICollection<>) ||
+ obType.GetGenericTypeDefinition() == typeof(IList<>))
+ {
+ //We could probably convert to a thin IEnumerable/IList/ICollection implementation around a python array
+ //but for simplicity right now convert to a List which is a copy of the python iterable.
+ return ToList(value, obType, out result, setError);
+ }
+ }
+
+ if (obType.FullName == "System.Action")
+ {
+ return ToAction(value, obType, out result, setError);
+ }
+
if (obType.IsEnum)
{
return ToEnum(value, obType, out result, setError);
@@ -847,6 +864,40 @@ private static void SetConversionError(IntPtr value, Type target)
Exceptions.SetError(Exceptions.TypeError, $"Cannot convert {src} to {target}");
}
+ private static bool ToListOfT(IntPtr value, Type elementType, out object result)
+ {
+ result = null;
+
+ IntPtr iterObject = Runtime.PyObject_GetIter(value);
+ if (iterObject == IntPtr.Zero)
+ return false;
+
+ bool IsSeqObj = Runtime.PySequence_Check(value);
+
+ try {
+ Type[] typeArgs = { elementType };
+ if (IsSeqObj) {
+ var listType = typeof(ListWrapper<>);
+ object listObj = Activator.CreateInstance(listType.MakeGenericType(typeArgs), new Object[] { value });
+ //IList list = (IList)Activator.CreateInstance(fullListType, new Object[] { value });
+
+ result = listObj;
+ }
+ else {
+ var listType = typeof(IterableWrapper<>);
+ IEnumerable list = (IEnumerable)Activator.CreateInstance(listType.MakeGenericType(typeArgs), new Object[] { value });
+ result = list;
+ }
+ }
+ catch (Exception) {
+ return false;
+
+ }
+ finally {
+ Runtime.XDecref(iterObject);
+ }
+ return true;
+ }
///
/// Convert a Python value to a correctly typed managed array instance.
@@ -857,47 +908,81 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s
{
Type elementType = obType.GetElementType();
result = null;
+ bool success = ToListOfT(value, elementType, out result);
+ if (!success)
+ {
+ if (setError)
+ SetConversionError(value, obType);
+ return false;
+ }
- bool IsSeqObj = Runtime.PySequence_Check(value);
- var len = IsSeqObj ? Runtime.PySequence_Size(value) : -1;
+ IList list = result as IList;
+ if (list == null) {
+ IEnumerable enumerable = (IEnumerable)result;
+ var listType = typeof(List<>);
+ var constructedListType = listType.MakeGenericType(elementType);
+ list = (IList)Activator.CreateInstance(constructedListType);
+ foreach (var item in enumerable) {
+ list.Add(item);
+ }
+ }
+ Array items = Array.CreateInstance(elementType, list.Count);
+ list.CopyTo(items, 0);
+ result = items;
+ return true;
+
+ }
- IntPtr IterObject = Runtime.PyObject_GetIter(value);
+ ///
+ /// Convert a Python function to a System.Action.
+ /// The Python value must support the Python "BLAH" protocol.
+ ///
+ private static bool ToAction(IntPtr value, Type obType, out object result, bool setError) {
- if(IterObject==IntPtr.Zero) {
- if (setError)
- {
+ result = null;
+
+ PyObject obj = new PyObject(value);
+
+ var args = obType.GetGenericArguments();
+
+ //Temporarily only deal with non-generic actions!
+ if (!obj.IsCallable() || args.Length != 0) {
+ if (setError) {
SetConversionError(value, obType);
}
return false;
}
- Array items;
-
- var listType = typeof(List<>);
- var constructedListType = listType.MakeGenericType(elementType);
- IList list = IsSeqObj ? (IList) Activator.CreateInstance(constructedListType, new Object[] {(int) len}) :
- (IList) Activator.CreateInstance(constructedListType);
- IntPtr item;
+ Action action = () => {
+ var y = Runtime.Refcount(value);
+ PyObject py_action = new PyObject(value);
+ var py_args = new PyObject[0];
+ var py_result = py_action.Invoke(py_args);
+ Runtime.XDecref(value);
+ };
+ var x = Runtime.Refcount(value);
+ Runtime.XIncref(value);
+ result = action;
- while ((item = Runtime.PyIter_Next(IterObject)) != IntPtr.Zero)
- {
- object obj = null;
+ return true;
+ }
- if (!Converter.ToManaged(item, elementType, out obj, true))
- {
- Runtime.XDecref(item);
- return false;
- }
- list.Add(obj);
- Runtime.XDecref(item);
+ ///
+ /// Convert a Python value to a correctly typed managed list instance.
+ /// The Python value must support the Python iterator protocol or and the
+ /// items in the sequence must be convertible to the target array type.
+ /// TODO - remove duplication with ToArray!
+ ///
+ private static bool ToList(IntPtr value, Type obType, out object result, bool setError) {
+ Type elementType = obType.GetGenericArguments()[0];
+ var success = ToListOfT(value, elementType, out result);
+ if (!success)
+ {
+ if (setError)
+ SetConversionError(value, obType);
+ return false;
}
- Runtime.XDecref(IterObject);
-
- items = Array.CreateInstance(elementType, list.Count);
- list.CopyTo(items, 0);
-
- result = items;
return true;
}
diff --git a/src/testing/methodtest.cs b/src/testing/methodtest.cs
index 91836b727..3a61a017c 100644
--- a/src/testing/methodtest.cs
+++ b/src/testing/methodtest.cs
@@ -117,6 +117,39 @@ public static int[] TestOverloadedParams(int v, int[] args)
return args;
}
+ public static int TestIList(System.Collections.Generic.IList