Skip to content

Commit 817e0e0

Browse files
committed
Added ability to define lifetime of the wrapped streams (#255)
1 parent 1c04819 commit 817e0e0

File tree

3 files changed

+63
-21
lines changed

3 files changed

+63
-21
lines changed

src/DotNext.IO/IO/SparseStream.cs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace DotNext.IO;
1111
/// <remarks>
1212
/// The stream is available for read-only operations.
1313
/// </remarks>
14-
internal abstract class SparseStream : Stream, IFlushable
14+
internal abstract class SparseStream(bool leaveOpen) : Stream, IFlushable
1515
{
1616
private int runningIndex;
1717

@@ -177,24 +177,60 @@ public sealed override IAsyncResult BeginWrite(byte[] buffer, int offset, int co
177177

178178
/// <inheritdoc/>
179179
public sealed override void EndWrite(IAsyncResult asyncResult) => throw new InvalidOperationException();
180+
181+
protected override void Dispose(bool disposing)
182+
{
183+
if (disposing && !leaveOpen)
184+
{
185+
Disposable.Dispose(Streams);
186+
}
187+
}
188+
189+
public override async ValueTask DisposeAsync()
190+
{
191+
for (var i = 0; i < Streams.Length; i++)
192+
{
193+
await Streams[i].DisposeAsync().ConfigureAwait(false);
194+
}
195+
196+
GC.SuppressFinalize(this);
197+
}
180198
}
181199

182-
internal sealed class SparseStream<T>(T streams) : SparseStream
200+
internal sealed class SparseStream<T>(T streams, bool leaveOpen) : SparseStream(leaveOpen)
183201
where T : struct, ITuple
184202
{
185203
protected override ReadOnlySpan<Stream> Streams
186204
=> MemoryMarshal.CreateReadOnlySpan(in Unsafe.As<T, Stream>(ref Unsafe.AsRef(in streams)), streams.Length);
187205
}
188206

189-
internal sealed class UnboundedSparseStream(ReadOnlySpan<Stream> streams) : SparseStream
207+
internal sealed class UnboundedSparseStream(ReadOnlySpan<Stream> streams, bool leaveOpen) : SparseStream(leaveOpen)
190208
{
191209
private MemoryOwner<Stream> streams = streams.Copy();
192210

193211
protected override ReadOnlySpan<Stream> Streams => streams.Span;
194212

195213
protected override void Dispose(bool disposing)
196214
{
197-
streams.Dispose();
198-
base.Dispose(disposing);
215+
try
216+
{
217+
base.Dispose(disposing);
218+
}
219+
finally
220+
{
221+
streams.Dispose();
222+
}
223+
}
224+
225+
public override async ValueTask DisposeAsync()
226+
{
227+
try
228+
{
229+
await base.DisposeAsync().ConfigureAwait(false);
230+
}
231+
finally
232+
{
233+
streams.Dispose();
234+
}
199235
}
200236
}

src/DotNext.IO/IO/StreamExtensions.cs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,43 +21,48 @@ internal static void ThrowIfEmpty<T>(in Memory<T> buffer, [CallerArgumentExpress
2121
throw new ArgumentException(ExceptionMessages.BufferTooSmall, expression);
2222
}
2323

24+
private static Stream Combine(Stream stream, ReadOnlySpan<Stream> others, bool leaveOpen)
25+
=> others switch
26+
{
27+
[] => stream,
28+
[var s] => new SparseStream<(Stream, Stream)>((stream, s), leaveOpen),
29+
[var s1, var s2] => new SparseStream<(Stream, Stream, Stream)>((stream, s1, s2), leaveOpen),
30+
[var s1, var s2, var s3] => new SparseStream<(Stream, Stream, Stream, Stream)>((stream, s1, s2, s3), leaveOpen),
31+
[var s1, var s2, var s3, var s4] => new SparseStream<(Stream, Stream, Stream, Stream, Stream)>((stream, s1, s2, s3, s4), leaveOpen),
32+
[var s1, var s2, var s3, var s4, var s5] => new SparseStream<(Stream, Stream, Stream, Stream, Stream, Stream)>((stream, s1, s2, s3, s4,
33+
s5), leaveOpen),
34+
_ => new UnboundedSparseStream(others, leaveOpen),
35+
};
36+
2437
/// <summary>
2538
/// Combines multiple readable streams.
2639
/// </summary>
2740
/// <param name="stream">The stream to combine.</param>
2841
/// <param name="others">A collection of streams.</param>
2942
/// <returns>An object that represents multiple streams as one logical stream.</returns>
3043
public static Stream Combine(this Stream stream, ReadOnlySpan<Stream> others) // TODO: Use params in future
31-
=> others switch
32-
{
33-
[] => stream,
34-
[var s] => new SparseStream<(Stream, Stream)>((stream, s)),
35-
[var s1, var s2] => new SparseStream<(Stream, Stream, Stream)>((stream, s1, s2)),
36-
[var s1, var s2, var s3] => new SparseStream<(Stream, Stream, Stream, Stream)>((stream, s1, s2, s3)),
37-
[var s1, var s2, var s3, var s4] => new SparseStream<(Stream, Stream, Stream, Stream, Stream)>((stream, s1, s2, s3, s4)),
38-
[var s1, var s2, var s3, var s4, var s5] => new SparseStream<(Stream, Stream, Stream, Stream, Stream, Stream)>((stream, s1, s2, s3, s4,
39-
s5)),
40-
_ => new UnboundedSparseStream(others.ToArray()),
41-
};
44+
=> Combine(stream, others, leaveOpen: true);
4245

4346
/// <summary>
4447
/// Combines multiple readable streams.
4548
/// </summary>
4649
/// <param name="streams">A collection of streams.</param>
50+
/// <param name="leaveOpen"><see langword="true"/> to keep the wrapped streams alive when combined stream disposed; otherwise, <see langword="false"/>.</param>
4751
/// <returns>An object that represents multiple streams as one logical stream.</returns>
4852
/// <exception cref="ArgumentException"><paramref name="streams"/> is empty.</exception>
49-
public static Stream Combine(this ReadOnlySpan<Stream> streams)
53+
public static Stream Combine(this ReadOnlySpan<Stream> streams, bool leaveOpen = true)
5054
=> streams is [var first, .. var rest]
51-
? Combine(first, rest)
55+
? Combine(first, rest, leaveOpen)
5256
: throw new ArgumentException(ExceptionMessages.BufferTooSmall, nameof(streams));
5357

5458
/// <summary>
5559
/// Combines multiple readable streams.
5660
/// </summary>
5761
/// <param name="streams">A collection of streams.</param>
62+
/// <param name="leaveOpen"><see langword="true"/> to keep the wrapped streams alive when combined stream disposed; otherwise, <see langword="false"/>.</param>
5863
/// <returns>An object that represents multiple streams as one logical stream.</returns>
5964
/// <exception cref="ArgumentException"><paramref name="streams"/> is empty.</exception>
60-
public static Stream Combine(this IEnumerable<Stream> streams)
65+
public static Stream Combine(this IEnumerable<Stream> streams, bool leaveOpen = true)
6166
{
6267
// Use buffer to allocate streams on the stack
6368
var buffer = new StreamBuffer();
@@ -67,7 +72,7 @@ public static Stream Combine(this IEnumerable<Stream> streams)
6772
try
6873
{
6974
writer.AddAll(streams);
70-
result = Combine(writer.WrittenSpan);
75+
result = Combine(writer.WrittenSpan, leaveOpen);
7176
}
7277
finally
7378
{

src/DotNext/Disposable.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ public static async ValueTask DisposeAsync(IEnumerable<IAsyncDisposable?> object
156156
/// Disposes many objects in safe manner.
157157
/// </summary>
158158
/// <param name="objects">An array of objects to dispose.</param>
159-
public static void Dispose(ReadOnlySpan<IDisposable?> objects)
159+
public static void Dispose<T>(ReadOnlySpan<T> objects)
160+
where T : IDisposable?
160161
{
161162
foreach (var obj in objects)
162163
obj?.Dispose();

0 commit comments

Comments
 (0)