Title: User encoding is skipped for elements iterated from non-generic .NET containers/enumerators · Issue #2679 · pythonnet/pythonnet · GitHub
Open Graph Title: User encoding is skipped for elements iterated from non-generic .NET containers/enumerators · Issue #2679 · pythonnet/pythonnet
X Title: User encoding is skipped for elements iterated from non-generic .NET containers/enumerators · Issue #2679 · pythonnet/pythonnet
Description: Environment Pythonnet version: 3.0.5 Python version: 3.12.9 Operating System: Linux (6.12.63-1-MANJARO # 1 SMP PREEMPT_DYNAMIC Thu, 18 Dec 2025 13:46:28 +0000 x86_64 GNU/Linux) .NET Runtime: coreclr 10.0.0 Details After creating and regi...
Open Graph Description: Environment Pythonnet version: 3.0.5 Python version: 3.12.9 Operating System: Linux (6.12.63-1-MANJARO # 1 SMP PREEMPT_DYNAMIC Thu, 18 Dec 2025 13:46:28 +0000 x86_64 GNU/Linux) .NET Runtime: corecl...
X Description: Environment Pythonnet version: 3.0.5 Python version: 3.12.9 Operating System: Linux (6.12.63-1-MANJARO # 1 SMP PREEMPT_DYNAMIC Thu, 18 Dec 2025 13:46:28 +0000 x86_64 GNU/Linux) .NET Runtime: corecl...
Opengraph URL: https://github.com/pythonnet/pythonnet/issues/2679
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"User encoding is skipped for elements iterated from non-generic .NET containers/enumerators","articleBody":"### Environment\n\n- Pythonnet version: 3.0.5\n- Python version: 3.12.9\n- Operating System: Linux (6.12.63-1-MANJARO # 1 SMP PREEMPT_DYNAMIC Thu, 18 Dec 2025 13:46:28 +0000 x86_64 GNU/Linux)\n- .NET Runtime: coreclr 10.0.0\n\n### Details\n\nAfter creating and registering an encoder (subclass of `IPyObjectEncoder`) to convert CLR objects to Python equivalents, iterating over non-generic containers yields CLR proxy objects instead of the converted Python object. However, doing the same for generic containers yields the converted objects as expected. In either case, accessing elements using the indexer produces a converted object.\n\nHere is a minimal test case demonstrating the issue:\n\n```python\nimport pytest\n\nimport System\nfrom Python.Test import CodecResetter\nfrom Python.Runtime import IPyObjectEncoder, PyObjectConversions\n\n@pytest.mark.parametrize('container_type', ['generic', 'non-generic'])\ndef test_iterator_element_conversion(container_type):\n \"\"\"Test iterator element conversion from Python.\"\"\"\n from Python.Test import Spam\n\n try:\n from Python.Test import IteratorElementEncoder\n except ImportError:\n class IteratorElementEncoder(IPyObjectEncoder):\n __namespace__ = \"Python.Test\"\n def CanEncode(self, clr_type):\n return clr_type.Name == \"Spam\" and clr_type.Namespace == \"Python.Test\"\n def TryEncode(self, clr_object):\n return clr_object.GetValue()\n\n spam_encoder = IteratorElementEncoder()\n PyObjectConversions.RegisterEncoder(spam_encoder)\n\n values = [\"first\", \"second\", \"third\"]\n if container_type == 'generic':\n container = System.Collections.Generic.List[Spam]()\n for value in values:\n container.Add(Spam(value))\n else:\n container = System.Array[Spam](Spam(v) for v in values)\n\n assert type(container[0]) is str\n assert next(iter(container.GetEnumerator())) == container[0]\n assert list(container.GetEnumerator()) == values\n\n CodecResetter.Reset()\n```\n\nAnd here are the results of running the test:\n\n```\n================================================================= FAILURES =================================================================\n______________________________________________ test_iterator_element_conversion[non-generic] _______________________________________________\n\n...\n assert type(container[0]) is str\n\u003e assert next(iter(container.GetEnumerator())) == container[0]\nE AssertionError: assert \u003cPython.Test.Spam object at 0x7ee403c447c0\u003e == 'first'\nE + where \u003cPython.Test.Spam object at 0x7ee403c447c0\u003e = next(\u003cCLR.Iterator object at 0x7ee403c449c0\u003e)\nE + where \u003cCLR.Iterator object at 0x7ee403c449c0\u003e = iter(\u003cSystem.Collections.IEnumerator object at 0x7ee403c46600\u003e)\nE + where \u003cSystem.Collections.IEnumerator object at 0x7ee403c46600\u003e = \u003cbound method 'GetEnumerator'\u003e()\nE + where \u003cbound method 'GetEnumerator'\u003e = \u003cPython.Test.Spam[] object at 0x7f25379bb540\u003e.GetEnumerator\n\ntests/test_conversion.py:786: AssertionError\n========================================================= short test summary info ==========================================================\nFAILED tests/test_conversion.py::test_iterator_element_conversion[non-generic] - AssertionError: assert \u003cPython.Test.Spam object at 0x7ee403c447c0\u003e == 'first'\n======================================================= 1 failed, 1 passed =======================================================\n\n```\n\nThe first assert passes for each test because the indexers of both generic and non-generic containers properly convert the object. The final two asserts both fail for the non-generic version because conversion is not performed, and the `Spam` proxies are returned instead of being converted to strings.\n\n### Implementation Details\n\nThe issue stems from `src/runtime/Types/ClassBase.cs:ClassBase.tp_iter_impl()`, which determines the generic type of a container, defaulting to `System.Object` for non-generic containers. The element type is then passed to `src/runtime/Types/Iterator.cs:Iterator` where it is used in calls to `Converter.ToPython()`. The problem is that `System.Object` is passed as the element type even though the type is actually something different that could potentially be converted by a user encoder. But `System.Object` types are not passed on to user converters and are returned as CLR proxies instead.\n\nThe solution is to check if the element type is `System.Object`, and query and use the actual value type instead.","author":{"url":"https://github.com/brandon-avantus","@type":"Person","name":"brandon-avantus"},"datePublished":"2026-01-14T18:24:37.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":3},"url":"https://github.com/2679/pythonnet/issues/2679"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:6a6b986f-b4bd-73cf-c0ef-2a36f7bd4238 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | B0AE:37D417:31C5CE8:32764BF:697032EA |
| html-safe-nonce | 243662ed0565e409b76e63a2fb3bcda7299a52f78d312c3ceadd169240a551f8 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCMEFFOjM3RDQxNzozMUM1Q0U4OjMyNzY0QkY6Njk3MDMyRUEiLCJ2aXNpdG9yX2lkIjoiODIzNDcwNDk3ODM2MjUxMjEwNiIsInJlZ2lvbl9lZGdlIjoic2VhIiwicmVnaW9uX3JlbmRlciI6InNlYSJ9 |
| visitor-hmac | 749214b3842cf84567bb40d5df2f0f4d184624403157b3ccdbe4a1b15e83c334 |
| hovercard-subject-tag | issue:3814360268 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/pythonnet/pythonnet/2679/issue_layout |
| twitter:image | https://opengraph.githubassets.com/dcfe938f4fde2845bf9da7fa6562627502fb63d7d62d2123b62cb9baa6b1e16c/pythonnet/pythonnet/issues/2679 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/dcfe938f4fde2845bf9da7fa6562627502fb63d7d62d2123b62cb9baa6b1e16c/pythonnet/pythonnet/issues/2679 |
| og:image:alt | Environment Pythonnet version: 3.0.5 Python version: 3.12.9 Operating System: Linux (6.12.63-1-MANJARO # 1 SMP PREEMPT_DYNAMIC Thu, 18 Dec 2025 13:46:28 +0000 x86_64 GNU/Linux) .NET Runtime: corecl... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | brandon-avantus |
| hostname | github.com |
| expected-hostname | github.com |
| None | 9920a62ba22d06470388e2904804fb7e5ec51c9e35f81784e9191394c74b2bd2 |
| turbo-cache-control | no-preview |
| go-import | github.com/pythonnet/pythonnet git https://github.com/pythonnet/pythonnet.git |
| octolytics-dimension-user_id | 6050430 |
| octolytics-dimension-user_login | pythonnet |
| octolytics-dimension-repository_id | 14748123 |
| octolytics-dimension-repository_nwo | pythonnet/pythonnet |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 14748123 |
| octolytics-dimension-repository_network_root_nwo | pythonnet/pythonnet |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | f643964067a552f02067066d6a910b2f90a5721f |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width