Add context manager protocol for .NET IDisposable types by den-run-ai · Pull Request #2568 · pythonnet/pythonnet · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AUTHORS.md
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].

### Added

- Add context manager protocol for .NET IDisposable types, allowing use of `with` statements for IDisposable objects (#9c73c35)
- Support `del obj[...]` for types derived from `IList<T>` and `IDictionary<K, V>`

### Changed
Expand Down
28 changes: 28 additions & 0 deletions doc/source/python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,34 @@ Python idioms:
for item in domain.GetAssemblies():
name = item.GetName()

Using Context Managers (IDisposable)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.NET types that implement ``IDisposable`` can be used with Python's context manager
protocol using the standard ``with`` statement. This automatically calls the object's
``Dispose()`` method when exiting the ``with`` block:

.. code:: python

from System.IO import MemoryStream, StreamWriter

# Use a MemoryStream as a context manager
with MemoryStream() as stream:
# The stream is automatically disposed when exiting the with block
writer = StreamWriter(stream)
writer.Write("Hello, context manager!")
writer.Flush()

# Do something with the stream
stream.Position = 0
# ...

# After exiting the with block, the stream is disposed
# Attempting to use it here would raise an exception

This works for any .NET type that implements ``IDisposable``, making resource
management much cleaner and safer in Python code.

Type Conversion
---------------

Expand Down
6 changes: 6 additions & 0 deletions src/runtime/Mixins/CollectionMixinsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public IEnumerable<PyType> GetBaseTypes(Type type, IList<PyType> existingBases)
newBases.Add(new PyType(this.Mixins.GetAttr("IteratorMixin")));
}

// context managers (for IDisposable)
if (interfaces.Contains(typeof(IDisposable)))
{
newBases.Add(new PyType(this.Mixins.GetAttr("ContextManagerMixin")));
}

if (newBases.Count == existingBases.Count)
{
return existingBases;
Expand Down
16 changes: 16 additions & 0 deletions src/runtime/Mixins/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@

import collections.abc as col

class ContextManagerMixin:
"""Implements Python's context manager protocol for .NET IDisposable types"""
def __enter__(self):
"""Return self for use in the with block"""
return self

def __exit__(self, exc_type, exc_val, exc_tb):
"""Call Dispose() when exiting the with block"""
if hasattr(self, 'Dispose'):
self.Dispose()
else:
from System import IDisposable
IDisposable(self).Dispose()
# Return False to indicate that exceptions should propagate
return False

class IteratorMixin(col.Iterator):
def close(self):
if hasattr(self, 'Dispose'):
Expand Down
118 changes: 118 additions & 0 deletions tests/test_disposable.py
Loading