diff --git a/AUTHORS.md b/AUTHORS.md
index 98cb1af39..5917944a8 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -28,6 +28,7 @@
- Jeff Reback ([@jreback](https://github.com/jreback))
- Joe Frayne ([@jfrayne](https://github.com/jfrayne))
- John Burnett ([@johnburnett](https://github.com/johnburnett))
+- John Wilkes ([@jbw3](https://github.com/jbw3))
- Luke Stratman ([@lstratman](https://github.com/lstratman))
- Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy))
- Matthias Dittrich ([@matthid](https://github.com/matthid))
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d39fc444..c30b4c393 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
- Added `clr.GetClrType` (#432, #433)
- Allowed passing `None` for nullable args (#460)
- Added keyword arguments based on C# syntax for calling CPython methods (#461)
+- Implemented GetDynamicMemberNames() for PyObject to allow dynamic object members to be visible in the debugger (#443)
### Changed
diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj
index fe02b0526..66e8c7165 100644
--- a/src/embed_tests/Python.EmbeddingTest.csproj
+++ b/src/embed_tests/Python.EmbeddingTest.csproj
@@ -93,6 +93,7 @@
+
diff --git a/src/embed_tests/TestPyObject.cs b/src/embed_tests/TestPyObject.cs
new file mode 100644
index 000000000..d794ce06e
--- /dev/null
+++ b/src/embed_tests/TestPyObject.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using Python.Runtime;
+
+namespace Python.EmbeddingTest
+{
+ public class TestPyObject
+ {
+ [OneTimeSetUp]
+ public void SetUp()
+ {
+ PythonEngine.Initialize();
+ }
+
+ [OneTimeTearDown]
+ public void Dispose()
+ {
+ PythonEngine.Shutdown();
+ }
+
+ [Test]
+ public void TestGetDynamicMemberNames()
+ {
+ List expectedMemberNames = new List
+ {
+ "add",
+ "getNumber",
+ "member1",
+ "member2"
+ };
+
+ PyDict locals = new PyDict();
+
+ PythonEngine.Exec(@"
+class MemberNamesTest(object):
+ def __init__(self):
+ self.member1 = 123
+ self.member2 = 'Test string'
+
+ def getNumber(self):
+ return 123
+
+ def add(self, x, y):
+ return x + y
+
+a = MemberNamesTest()
+", null, locals.Handle);
+
+ PyObject a = locals.GetItem("a");
+
+ IEnumerable memberNames = a.GetDynamicMemberNames();
+
+ foreach (string expectedName in expectedMemberNames)
+ {
+ Assert.IsTrue(memberNames.Contains(expectedName), "Could not find member '{0}'.", expectedName);
+ }
+ }
+ }
+}
diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs
index 4da74f96a..0e075824a 100644
--- a/src/runtime/pyobject.cs
+++ b/src/runtime/pyobject.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections;
+using System.Collections.Generic;
using System.Dynamic;
using System.Linq.Expressions;
@@ -1238,5 +1239,20 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out object r
result = CheckNone(new PyObject(res));
return true;
}
+
+ ///
+ /// Returns the enumeration of all dynamic member names.
+ ///
+ ///
+ /// This method exists for debugging purposes only.
+ ///
+ /// A sequence that contains dynamic member names.
+ public override IEnumerable GetDynamicMemberNames()
+ {
+ foreach (PyObject pyObj in Dir())
+ {
+ yield return pyObj.ToString();
+ }
+ }
}
}