Add MemoryBackedStream - #90
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds Stream extension methods to materialize any stream into a MemoryStream, providing both synchronous and asynchronous variants consistent with existing stream helper patterns in the codebase.
Changes:
- Added
ToMemoryStream()to copy a stream into a rewoundMemoryStream(with a fast-path forMemoryStream). - Added
ToMemoryStreamAsync(CancellationToken)to perform the same operation asynchronously (guarded by target framework defines). - Added unit tests covering sync/async and copy-path/fast-path behavior using a non-
MemoryStreamwrapper.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| PowerKit/Extensions/StreamExtensions.cs | Adds ToMemoryStream() / ToMemoryStreamAsync() stream materialization helpers. |
| PowerKit.Tests/Extensions/StreamExtensionsTests.cs | Adds tests validating both methods across copy and fast-path scenarios. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@copilot actually, can we re-architect this so that we have a unified abstraction:
|
Done. I've replaced
|
|
@copilot please fix merge conflicts |
…y-stream-extension # Conflicts: # PowerKit/Extensions/StreamExtensions.cs
Done — merged |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## prime #90 +/- ##
==========================================
- Coverage 87.18% 86.36% -0.83%
==========================================
Files 76 78 +2
Lines 1358 1393 +35
Branches 236 239 +3
==========================================
+ Hits 1184 1203 +19
- Misses 122 137 +15
- Partials 52 53 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@copilot please update the PR description |
Done — updated the PR description to reflect the current |
|
@copilot I still see the old description. Post the updated description here and I'll update it. |
| var data = new byte[] { 1, 2, 3, 4, 5 }; | ||
| using var tempFile = TempFile.Create(); | ||
| File.WriteAllBytes(tempFile.Path, data); | ||
| using var source = File.OpenRead(tempFile.Path); |
There was a problem hiding this comment.
You're right — FileStream is seekable. Replaced it with a private NonSeekableStream helper that wraps a MemoryStream and returns false for CanSeek, so the test now genuinely exercises the unseekable-source path.
|
|
||
| public class MemoryReadStreamTests | ||
| { | ||
| private sealed class NonSeekableStream(Stream inner) : Stream |
There was a problem hiding this comment.
Extract as a utility and use it for write tests too @copilot
There was a problem hiding this comment.
Done — moved NonSeekableStream into its own NonSeekableStream.cs file in the test project, removed the private class from MemoryReadStreamTests, and updated MemoryWriteStreamTests to also use it (wrapping a MemoryStream instead of File.OpenWrite).
Adds a
MemoryBackedStreamwrapper and aToMemoryStream()extension method onStreamthat lazily back any stream with an in-memoryMemoryStream.Changes
StreamExtensions.cs— newfile-scopedMemoryBackedStreamclass andToMemoryStream()extension:MemoryBackedStreamis aStreamwrapper that lazily loads the underlying stream into aMemoryStreamon the first read, write, or seek.CanSeek = true), regardless of whether the underlying stream supports seeking.Dispose(), the full buffer is written back to the underlying stream.Dispose()seeks the underlying stream to the beginning and truncates it to the buffer's length before writing back, ensuring the original content is completely replaced.ToMemoryStream()returns the source as-is if it is already aMemoryStream(no wrapping).StreamExtensionsTests.cs— tests covering all major code paths:ReadOnlyStreamwrapper (non-seekable) — verifies reads and that the wrapper is seekableMemoryStreamfast-path — verifies the source is returned as-isWriteOnlyStreamwrapper — verifies write-back on disposeReadWriteStreamwrapper (readable + seekable + writable) — verifies full-stream load with position restore and complete write-back on disposeUsage