Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9c73c35

Browse files
den-run-aiden-run-ai
authored andcommitted
Add context manager protocol for .NET IDisposable types
1 parent 030a9f9 commit 9c73c35

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

src/runtime/Mixins/CollectionMixinsProvider.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ public IEnumerable<PyType> GetBaseTypes(Type type, IList<PyType> existingBases)
6363
newBases.Add(new PyType(this.Mixins.GetAttr("IteratorMixin")));
6464
}
6565

66+
// context managers (for IDisposable)
67+
if (interfaces.Contains(typeof(IDisposable)))
68+
{
69+
newBases.Add(new PyType(this.Mixins.GetAttr("ContextManagerMixin")));
70+
}
71+
6672
if (newBases.Count == existingBases.Count)
6773
{
6874
return existingBases;

src/runtime/Mixins/collections.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@
55

66
import collections.abc as col
77

8+
class ContextManagerMixin:
9+
"""Implements Python's context manager protocol for .NET IDisposable types"""
10+
def __enter__(self):
11+
"""Return self for use in the with block"""
12+
return self
13+
14+
def __exit__(self, exc_type, exc_val, exc_tb):
15+
"""Call Dispose() when exiting the with block"""
16+
if hasattr(self, 'Dispose'):
17+
self.Dispose()
18+
else:
19+
from System import IDisposable
20+
IDisposable(self).Dispose()
21+
# Return False to indicate that exceptions should propagate
22+
return False
23+
824
class IteratorMixin(col.Iterator):
925
def close(self):
1026
if hasattr(self, 'Dispose'):

tests/test_disposable.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import os
2+
import unittest
3+
import clr
4+
5+
# Import required .NET namespaces
6+
clr.AddReference("System")
7+
clr.AddReference("System.IO")
8+
from System import IDisposable
9+
from System.IO import MemoryStream, FileStream, FileMode, File, Path, StreamWriter
10+
11+
class DisposableContextManagerTests(unittest.TestCase):
12+
"""Tests for Python's context manager protocol with .NET IDisposable objects"""
13+
14+
def test_memory_stream_context_manager(self):
15+
"""Test that MemoryStream can be used as a context manager"""
16+
data = bytes([1, 2, 3, 4, 5])
17+
18+
# Using with statement with MemoryStream
19+
with MemoryStream() as stream:
20+
# Convert Python bytes to .NET byte array for proper writing
21+
from System import Array, Byte
22+
dotnet_bytes = Array[Byte](data)
23+
stream.Write(dotnet_bytes, 0, len(dotnet_bytes))
24+
25+
self.assertEqual(5, stream.Length)
26+
stream.Position = 0
27+
28+
# Create a .NET byte array to read into
29+
buffer = Array[Byte](5)
30+
stream.Read(buffer, 0, 5)
31+
32+
# Convert back to Python bytes for comparison
33+
result = bytes(buffer)
34+
self.assertEqual(data, result)
35+
36+
# The stream should be disposed (closed) after the with block
37+
with self.assertRaises(Exception):
38+
stream.Position = 0 # This should fail because the stream is closed
39+
40+
def test_file_stream_context_manager(self):
41+
"""Test that FileStream can be used as a context manager"""
42+
# Create a temporary file path
43+
temp_path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())
44+
45+
try:
46+
# Write data to the file using with statement
47+
data = "Hello, context manager!"
48+
with FileStream(temp_path, FileMode.Create) as fs:
49+
writer = StreamWriter(fs)
50+
writer.Write(data)
51+
writer.Flush()
52+
53+
# Verify the file was written and stream was closed
54+
self.assertTrue(File.Exists(temp_path))
55+
content = File.ReadAllText(temp_path)
56+
self.assertEqual(data, content)
57+
58+
# The stream should be disposed after the with block
59+
with self.assertRaises(Exception):
60+
fs.Position = 0 # This should fail because the stream is closed
61+
finally:
62+
# Clean up
63+
if File.Exists(temp_path):
64+
File.Delete(temp_path)
65+
66+
def test_disposable_in_multiple_contexts(self):
67+
"""Test that using .NET IDisposable objects in multiple contexts works correctly"""
68+
# Create multiple streams and check that they're all properly disposed
69+
70+
# Create a list to track if streams were properly disposed
71+
# (we'll check this by trying to access the stream after disposal)
72+
streams_disposed = [False, False]
73+
74+
# Use nested context managers with .NET IDisposable objects
75+
with MemoryStream() as outer_stream:
76+
# Write some data to the outer stream
77+
from System import Array, Byte
78+
outer_data = Array[Byte]([10, 20, 30])
79+
outer_stream.Write(outer_data, 0, len(outer_data))
80+
81+
# Check that the outer stream is usable
82+
self.assertEqual(3, outer_stream.Length)
83+
84+
with MemoryStream() as inner_stream:
85+
# Write different data to the inner stream
86+
inner_data = Array[Byte]([40, 50, 60, 70])
87+
inner_stream.Write(inner_data, 0, len(inner_data))
88+
89+
# Check that the inner stream is usable
90+
self.assertEqual(4, inner_stream.Length)
91+
92+
# Try to use the inner stream - should fail because it's disposed
93+
try:
94+
inner_stream.Position = 0
95+
except Exception:
96+
streams_disposed[1] = True
97+
98+
# Try to use the outer stream - should fail because it's disposed
99+
try:
100+
outer_stream.Position = 0
101+
except Exception:
102+
streams_disposed[0] = True
103+
104+
# Verify both streams were properly disposed
105+
self.assertTrue(all(streams_disposed))
106+
107+
def test_exception_handling(self):
108+
"""Test that exceptions propagate correctly through the context manager"""
109+
with self.assertRaises(ValueError):
110+
with MemoryStream() as stream:
111+
raise ValueError("Test exception")
112+
113+
# Stream should be disposed despite the exception
114+
with self.assertRaises(Exception):
115+
stream.Position = 0
116+
117+
if __name__ == "__main__":
118+
unittest.main()

0 commit comments

Comments
 (0)