Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add context manager protocol for .NET IDisposable types#2568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
den-run-ai wants to merge3 commits intopythonnet:master
base:master
Choose a base branch
Loading
fromden-run-ai:feature/context-manager-disposable
Open
Show file tree
Hide file tree
Changes fromall 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 deletionAUTHORS.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,7 @@
- Barton Cline ([@BartonCline](https://github.com/BartonCline))
- Brian Lloyd ([@brianlloyd](https://github.com/brianlloyd))
- David Anthoff ([@davidanthoff](https://github.com/davidanthoff))
- Denis Akhiyarov ([@denfromufa](https://github.com/denfromufa))
- Denis Akhiyarov ([@den-run-ai](https://github.com/den-run-ai))
- Tony Roberts ([@tonyroberts](https://github.com/tonyroberts))
- Victor Uriarte ([@vmuriart](https://github.com/vmuriart))

Expand Down
3 changes: 3 additions & 0 deletionsCHANGELOG.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
## Unreleased

### Added

- Add context manager protocol for .NET IDisposable types, allowing use of `with` statements for IDisposable objects (#9c73c35)

### Changed
### Fixed

Expand Down
28 changes: 28 additions & 0 deletionsdoc/source/python.rst
View file
Open in desktop
Original file line numberDiff line numberDiff 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 deletionssrc/runtime/Mixins/CollectionMixinsProvider.cs
View file
Open in desktop
Original file line numberDiff line numberDiff 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 deletionssrc/runtime/Mixins/collections.py
View file
Open in desktop
Original file line numberDiff line numberDiff 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 deletionstests/test_disposable.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
import os
import unittest
import clr

# Import required .NET namespaces
clr.AddReference("System")
clr.AddReference("System.IO")
from System import IDisposable
from System.IO import MemoryStream, FileStream, FileMode, File, Path, StreamWriter

class DisposableContextManagerTests(unittest.TestCase):
"""Tests for Python's context manager protocol with .NET IDisposable objects"""

def test_memory_stream_context_manager(self):
"""Test that MemoryStream can be used as a context manager"""
data = bytes([1, 2, 3, 4, 5])

# Using with statement with MemoryStream
with MemoryStream() as stream:
# Convert Python bytes to .NET byte array for proper writing
from System import Array, Byte
dotnet_bytes = Array[Byte](data)
stream.Write(dotnet_bytes, 0, len(dotnet_bytes))

self.assertEqual(5, stream.Length)
stream.Position = 0

# Create a .NET byte array to read into
buffer = Array[Byte](5)
stream.Read(buffer, 0, 5)

# Convert back to Python bytes for comparison
result = bytes(buffer)
self.assertEqual(data, result)

# The stream should be disposed (closed) after the with block
with self.assertRaises(Exception):
stream.Position = 0 # This should fail because the stream is closed

def test_file_stream_context_manager(self):
"""Test that FileStream can be used as a context manager"""
# Create a temporary file path
temp_path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())

try:
# Write data to the file using with statement
data = "Hello, context manager!"
with FileStream(temp_path, FileMode.Create) as fs:
writer = StreamWriter(fs)
writer.Write(data)
writer.Flush()

# Verify the file was written and stream was closed
self.assertTrue(File.Exists(temp_path))
content = File.ReadAllText(temp_path)
self.assertEqual(data, content)

# The stream should be disposed after the with block
with self.assertRaises(Exception):
fs.Position = 0 # This should fail because the stream is closed
finally:
# Clean up
if File.Exists(temp_path):
File.Delete(temp_path)

def test_disposable_in_multiple_contexts(self):
"""Test that using .NET IDisposable objects in multiple contexts works correctly"""
# Create multiple streams and check that they're all properly disposed

# Create a list to track if streams were properly disposed
# (we'll check this by trying to access the stream after disposal)
streams_disposed = [False, False]

# Use nested context managers with .NET IDisposable objects
with MemoryStream() as outer_stream:
# Write some data to the outer stream
from System import Array, Byte
outer_data = Array[Byte]([10, 20, 30])
outer_stream.Write(outer_data, 0, len(outer_data))

# Check that the outer stream is usable
self.assertEqual(3, outer_stream.Length)

with MemoryStream() as inner_stream:
# Write different data to the inner stream
inner_data = Array[Byte]([40, 50, 60, 70])
inner_stream.Write(inner_data, 0, len(inner_data))

# Check that the inner stream is usable
self.assertEqual(4, inner_stream.Length)

# Try to use the inner stream - should fail because it's disposed
try:
inner_stream.Position = 0
except Exception:
streams_disposed[1] = True

# Try to use the outer stream - should fail because it's disposed
try:
outer_stream.Position = 0
except Exception:
streams_disposed[0] = True

# Verify both streams were properly disposed
self.assertTrue(all(streams_disposed))

def test_exception_handling(self):
"""Test that exceptions propagate correctly through the context manager"""
with self.assertRaises(ValueError):
with MemoryStream() as stream:
raise ValueError("Test exception")

# Stream should be disposed despite the exception
with self.assertRaises(Exception):
stream.Position = 0

if __name__ == "__main__":
unittest.main()
Loading

[8]ページ先頭

©2009-2025 Movatter.jp