Add Stream wrappers for memory and text-based types#126669
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces new standardized Stream wrapper types across CoreLib and System.Memory, aligning with the approved API shape from #82801 and updating a few internal call sites to use the new wrappers.
Changes:
- Added new public sealed stream wrappers:
StringStream,ReadOnlyMemoryStream,WritableMemoryStream(CoreLib) andReadOnlySequenceStream(System.Memory). - Updated reference assemblies and project files to expose/compile the new APIs.
- Added conformance + targeted behavioral tests, and updated select internal consumers to use
StringStream.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Private.CoreLib/src/System/IO/StringStream.cs | Implements the new StringStream API (read-only, non-seekable, on-the-fly encoding). |
| src/libraries/System.Private.CoreLib/src/System/IO/ReadOnlyMemoryStream.cs | Implements seekable read-only stream over ReadOnlyMemory<byte>. |
| src/libraries/System.Private.CoreLib/src/System/IO/WritableMemoryStream.cs | Implements seekable fixed-capacity read/write stream over Memory<byte>. |
| src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems | Wires new CoreLib stream wrapper source files into the build. |
| src/libraries/System.Private.CoreLib/src/Resources/Strings.resx | Adds a new resource string entry (currently appears unused). |
| src/libraries/System.Runtime/ref/System.Runtime.cs | Updates public API surface to include the new stream wrapper types (and a small ref signature normalization change). |
| src/libraries/System.Runtime/tests/System.IO.Tests/System.IO.Tests.csproj | Includes the new test files in the System.IO test project. |
| src/libraries/System.Runtime/tests/System.IO.Tests/StringStream/StringStreamConformanceTests.cs | Adds conformance coverage for StringStream (string + ReadOnlyMemory<char> overloads). |
| src/libraries/System.Runtime/tests/System.IO.Tests/StringStream/StringStreamTests_String.cs | Adds targeted StringStream(string, Encoding) behavioral tests. |
| src/libraries/System.Runtime/tests/System.IO.Tests/StringStream/StringStreamTests_Memory.cs | Adds targeted StringStream(ReadOnlyMemory<char>, Encoding) behavioral tests. |
| src/libraries/System.Runtime/tests/System.IO.Tests/ReadOnlyMemoryStream/ReadOnlyMemoryStreamConformanceTests.cs | Adds conformance coverage for ReadOnlyMemoryStream. |
| src/libraries/System.Runtime/tests/System.IO.Tests/ReadOnlyMemoryStream/ReadOnlyMemoryStreamTests.cs | Adds targeted ReadOnlyMemoryStream behavioral tests. |
| src/libraries/System.Runtime/tests/System.IO.Tests/WritableMemoryStream/WritableMemoryStreamConformanceTests.cs | Adds conformance coverage for WritableMemoryStream (with some overridden tests). |
| src/libraries/System.Runtime/tests/System.IO.Tests/WritableMemoryStream/WritableMemoryStreamTests.cs | Adds targeted WritableMemoryStream behavioral tests. |
| src/libraries/System.Memory/src/System/Buffers/ReadOnlySequenceStream.cs | Implements ReadOnlySequenceStream over ReadOnlySequence<byte>. |
| src/libraries/System.Memory/src/System.Memory.csproj | Includes the new ReadOnlySequenceStream source file in System.Memory. |
| src/libraries/System.Memory/src/Resources/Strings.resx | Adds SR strings needed for stream-like exception messages in System.Memory. |
| src/libraries/System.Memory/ref/System.Memory.cs | Adds ReadOnlySequenceStream to the System.Memory ref surface. |
| src/libraries/System.Memory/tests/System.Memory.Tests.csproj | Adds tests for ReadOnlySequenceStream and references StreamConformanceTests. |
| src/libraries/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceStreamTests.cs | Adds targeted behavioral tests for multi-segment ReadOnlySequenceStream scenarios. |
| src/libraries/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceStream.ConformanceTests.cs | Adds conformance coverage for ReadOnlySequenceStream. |
| src/libraries/System.Private.Xml/src/System/Xml/Resolvers/XmlPreloadedResolver.cs | Switches internal string-to-stream conversion to StringStream. |
| src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonXmlDataContract.cs | Switches internal string-to-stream conversion to StringStream. |
Comments suppressed due to low confidence (1)
src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonXmlDataContract.cs:38
- Variable is still named 'memoryStream' but the implementation is now StringStream (not a MemoryStream). Rename the local to avoid implying the stream is seekable/in-memory-buffered (e.g., 'xmlContentStream').
Stream memoryStream = new StringStream(xmlContent, Encoding.UTF8);
object? xmlValue;
XmlDictionaryReaderQuotas? quotas = ((JsonReaderDelegator)jsonReader).ReaderQuotas;
if (quotas == null)
{
xmlValue = dataContractSerializer.ReadObject(memoryStream);
27761ff to
b43698b
Compare
b43698b to
95a2989
Compare
95a2989 to
5e3f98c
Compare
…onvert edge case. Flush strategy update.
5e3f98c to
fec7a59
Compare
…pers drop duplicate field and use _isOpen directly; align ROSS Seek overflow message with MemoryStream
|
@EgorBot -linux_amd -osx_arm64 // Compares two design shapes for ReadOnlyMemoryStream / WritableMemoryStream from
// dotnet/runtime#126669:
// *M — derives from MemoryStream (the PR's shape).
// In the actual PR, MemoryStream._position / _length / _writable / _isOpen
// were changed to "private protected" so the derived wrapper can read/write
// base state directly with no extra fields. Inline code outside CoreLib can
// not access "private protected" base fields, so this inline *M variant
// carries its own 4-byte _position (and _length for the writable variant) —
// a constant per-instance offset over the real PR types.
// *S — derives directly from Stream with its own _position / _length / _isOpen.
// Baseline: stock MemoryStream(byte[], ...).
//
// Note: the previous attempt referenced ReadOnlyMemoryStream / WritableMemoryStream
// directly. Build failed CS0246 because those PR types are not in the installed SDK
// reference assemblies — EgorBot compiles benchmark snippets against stock SDK refs.
// Inlining both candidate shapes here removes that dependency.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Columns;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
[MemoryDiagnoser]
[HideColumns(Column.Error, Column.StdDev, Column.Median, Column.RatioSD)]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class CreationGCPressureBenchmarks
{
private byte[] _small;
private byte[] _medium;
[GlobalSetup]
public void Setup()
{
_small = new byte[64];
_medium = new byte[1024];
new Random(42).NextBytes(_small);
new Random(42).NextBytes(_medium);
}
[Benchmark(Baseline = true)]
[BenchmarkCategory("ReadOnly_Construct_Small")]
public object ROConstruct_M_Small() => new ReadOnlyMemoryStreamM(_small);
[Benchmark]
[BenchmarkCategory("ReadOnly_Construct_Small")]
public object ROConstruct_S_Small() => new ReadOnlyMemoryStreamS(_small);
[Benchmark]
[BenchmarkCategory("ReadOnly_Construct_Small")]
public object ROConstruct_Baseline_Small() => new MemoryStream(_small, writable: false);
[Benchmark(Baseline = true)]
[BenchmarkCategory("ReadOnly_Construct_Medium")]
public object ROConstruct_M_Medium() => new ReadOnlyMemoryStreamM(_medium);
[Benchmark]
[BenchmarkCategory("ReadOnly_Construct_Medium")]
public object ROConstruct_S_Medium() => new ReadOnlyMemoryStreamS(_medium);
[Benchmark]
[BenchmarkCategory("ReadOnly_Construct_Medium")]
public object ROConstruct_Baseline_Medium() => new MemoryStream(_medium, writable: false);
[Benchmark(Baseline = true)]
[BenchmarkCategory("ReadOnly_BulkCreate_100K_Small")]
public int ROBulk_M_Small_100K()
{
int sum = 0; byte[] data = _small;
for (int i = 0; i < 100_000; i++) { var s = new ReadOnlyMemoryStreamM(data); sum += s.ReadByte(); }
return sum;
}
[Benchmark]
[BenchmarkCategory("ReadOnly_BulkCreate_100K_Small")]
public int ROBulk_S_Small_100K()
{
int sum = 0; byte[] data = _small;
for (int i = 0; i < 100_000; i++) { var s = new ReadOnlyMemoryStreamS(data); sum += s.ReadByte(); }
return sum;
}
[Benchmark]
[BenchmarkCategory("ReadOnly_BulkCreate_100K_Small")]
public int ROBulk_Baseline_Small_100K()
{
int sum = 0; byte[] data = _small;
for (int i = 0; i < 100_000; i++) { var s = new MemoryStream(data, writable: false); sum += s.ReadByte(); }
return sum;
}
[Benchmark(Baseline = true)]
[BenchmarkCategory("ReadOnly_BulkCreate_100K_Medium")]
public int ROBulk_M_Medium_100K()
{
int sum = 0; byte[] data = _medium;
for (int i = 0; i < 100_000; i++) { var s = new ReadOnlyMemoryStreamM(data); sum += s.ReadByte(); }
return sum;
}
[Benchmark]
[BenchmarkCategory("ReadOnly_BulkCreate_100K_Medium")]
public int ROBulk_S_Medium_100K()
{
int sum = 0; byte[] data = _medium;
for (int i = 0; i < 100_000; i++) { var s = new ReadOnlyMemoryStreamS(data); sum += s.ReadByte(); }
return sum;
}
[Benchmark]
[BenchmarkCategory("ReadOnly_BulkCreate_100K_Medium")]
public int ROBulk_Baseline_Medium_100K()
{
int sum = 0; byte[] data = _medium;
for (int i = 0; i < 100_000; i++) { var s = new MemoryStream(data, writable: false); sum += s.ReadByte(); }
return sum;
}
[Benchmark(Baseline = true)]
[BenchmarkCategory("ReadOnly_BulkCreate_1M")]
public int ROBulk_M_1M()
{
int sum = 0; byte[] data = _small;
for (int i = 0; i < 1_000_000; i++) { var s = new ReadOnlyMemoryStreamM(data); sum += s.ReadByte(); }
return sum;
}
[Benchmark]
[BenchmarkCategory("ReadOnly_BulkCreate_1M")]
public int ROBulk_S_1M()
{
int sum = 0; byte[] data = _small;
for (int i = 0; i < 1_000_000; i++) { var s = new ReadOnlyMemoryStreamS(data); sum += s.ReadByte(); }
return sum;
}
[Benchmark]
[BenchmarkCategory("ReadOnly_BulkCreate_1M")]
public int ROBulk_Baseline_1M()
{
int sum = 0; byte[] data = _small;
for (int i = 0; i < 1_000_000; i++) { var s = new MemoryStream(data, writable: false); sum += s.ReadByte(); }
return sum;
}
[Benchmark(Baseline = true)]
[BenchmarkCategory("Writable_Construct_Small")]
public object WConstruct_M_Small() => new WritableMemoryStreamM(_small);
[Benchmark]
[BenchmarkCategory("Writable_Construct_Small")]
public object WConstruct_S_Small() => new WritableMemoryStreamS(_small);
[Benchmark]
[BenchmarkCategory("Writable_Construct_Small")]
public object WConstruct_Baseline_Small() => new MemoryStream(_small);
[Benchmark(Baseline = true)]
[BenchmarkCategory("Writable_Construct_Medium")]
public object WConstruct_M_Medium() => new WritableMemoryStreamM(_medium);
[Benchmark]
[BenchmarkCategory("Writable_Construct_Medium")]
public object WConstruct_S_Medium() => new WritableMemoryStreamS(_medium);
[Benchmark]
[BenchmarkCategory("Writable_Construct_Medium")]
public object WConstruct_Baseline_Medium() => new MemoryStream(_medium);
[Benchmark(Baseline = true)]
[BenchmarkCategory("Writable_BulkCreate_100K_Medium")]
public int WBulk_M_Medium_100K()
{
int sum = 0; byte[] data = _medium;
for (int i = 0; i < 100_000; i++) { var s = new WritableMemoryStreamM(data); sum += s.ReadByte(); }
return sum;
}
[Benchmark]
[BenchmarkCategory("Writable_BulkCreate_100K_Medium")]
public int WBulk_S_Medium_100K()
{
int sum = 0; byte[] data = _medium;
for (int i = 0; i < 100_000; i++) { var s = new WritableMemoryStreamS(data); sum += s.ReadByte(); }
return sum;
}
[Benchmark]
[BenchmarkCategory("Writable_BulkCreate_100K_Medium")]
public int WBulk_Baseline_Medium_100K()
{
int sum = 0; byte[] data = _medium;
for (int i = 0; i < 100_000; i++) { var s = new MemoryStream(data); sum += s.ReadByte(); }
return sum;
}
[Benchmark(Baseline = true)]
[BenchmarkCategory("Writable_BulkCreate_1M")]
public int WBulk_M_1M()
{
int sum = 0; byte[] data = _small;
for (int i = 0; i < 1_000_000; i++) { var s = new WritableMemoryStreamM(data); sum += s.ReadByte(); }
return sum;
}
[Benchmark]
[BenchmarkCategory("Writable_BulkCreate_1M")]
public int WBulk_S_1M()
{
int sum = 0; byte[] data = _small;
for (int i = 0; i < 1_000_000; i++) { var s = new WritableMemoryStreamS(data); sum += s.ReadByte(); }
return sum;
}
[Benchmark]
[BenchmarkCategory("Writable_BulkCreate_1M")]
public int WBulk_Baseline_1M()
{
int sum = 0; byte[] data = _small;
for (int i = 0; i < 1_000_000; i++) { var s = new MemoryStream(data); sum += s.ReadByte(); }
return sum;
}
}
// ===========================================================
// "M" variants — derive from MemoryStream (PR shape).
// Constructor uses : base(0), equivalent to the PR's : base()
// which chains to MemoryStream() -> this(0).
// ===========================================================
public sealed class ReadOnlyMemoryStreamM : MemoryStream
{
private ReadOnlyMemory<byte> _memory;
private int _positionM;
public ReadOnlyMemoryStreamM(ReadOnlyMemory<byte> source) : base(0)
{
_memory = source;
}
public override bool CanWrite => false;
public override int Capacity
{
get => _memory.Length;
set => throw new NotSupportedException();
}
public override long Length => _memory.Length;
public override long Position
{
get => _positionM;
set
{
ArgumentOutOfRangeException.ThrowIfNegative(value);
_positionM = (int)value;
}
}
public override int ReadByte()
{
ReadOnlySpan<byte> span = _memory.Span;
int pos = _positionM;
if ((uint)pos >= (uint)span.Length) return -1;
_positionM = pos + 1;
return span[pos];
}
public override int Read(byte[] buffer, int offset, int count)
{
ReadOnlySpan<byte> src = _memory.Span;
int pos = _positionM;
int available = src.Length - pos;
if (available <= 0) return 0;
int toCopy = Math.Min(available, count);
src.Slice(pos, toCopy).CopyTo(buffer.AsSpan(offset, toCopy));
_positionM = pos + toCopy;
return toCopy;
}
public override long Seek(long offset, SeekOrigin origin)
{
long target = origin switch
{
SeekOrigin.Begin => offset,
SeekOrigin.Current => _positionM + offset,
SeekOrigin.End => _memory.Length + offset,
_ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin))
};
ArgumentOutOfRangeException.ThrowIfNegative(target);
_positionM = (int)target;
return _positionM;
}
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
public override byte[] GetBuffer() => throw new UnauthorizedAccessException();
public override bool TryGetBuffer(out ArraySegment<byte> buffer) { buffer = default; return false; }
public override void Flush() { }
}
public sealed class WritableMemoryStreamM : MemoryStream
{
private Memory<byte> _memory;
private int _positionM;
private int _lengthM;
public WritableMemoryStreamM(Memory<byte> buffer) : base(0)
{
_memory = buffer;
}
public override int Capacity
{
get => _memory.Length;
set => throw new NotSupportedException();
}
public override long Length => _lengthM;
public override long Position
{
get => _positionM;
set
{
ArgumentOutOfRangeException.ThrowIfNegative(value);
_positionM = (int)value;
}
}
public override int ReadByte()
{
ReadOnlySpan<byte> span = _memory.Span;
int pos = _positionM;
if ((uint)pos < (uint)_lengthM)
{
_positionM = pos + 1;
return span[pos];
}
return -1;
}
public override int Read(byte[] buffer, int offset, int count)
{
int remaining = _lengthM - _positionM;
if (remaining <= 0 || count == 0) return 0;
int toCopy = Math.Min(remaining, count);
_memory.Span.Slice(_positionM, toCopy).CopyTo(buffer.AsSpan(offset, toCopy));
_positionM += toCopy;
return toCopy;
}
public override void WriteByte(byte value)
{
if (_positionM >= _memory.Length)
throw new NotSupportedException("Stream is not expandable.");
_memory.Span[_positionM++] = value;
if (_positionM > _lengthM) _lengthM = _positionM;
}
public override void Write(byte[] buffer, int offset, int count)
{
if (count == 0) return;
if (_positionM > _memory.Length - count)
throw new NotSupportedException("Stream is not expandable.");
buffer.AsSpan(offset, count).CopyTo(_memory.Span.Slice(_positionM));
_positionM += count;
if (_positionM > _lengthM) _lengthM = _positionM;
}
public override long Seek(long offset, SeekOrigin origin)
{
long target = origin switch
{
SeekOrigin.Begin => offset,
SeekOrigin.Current => _positionM + offset,
SeekOrigin.End => _lengthM + offset,
_ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin))
};
ArgumentOutOfRangeException.ThrowIfNegative(target);
_positionM = (int)target;
return _positionM;
}
public override void SetLength(long value) => throw new NotSupportedException();
public override byte[] GetBuffer() => throw new UnauthorizedAccessException();
public override bool TryGetBuffer(out ArraySegment<byte> buffer) { buffer = default; return false; }
public override void Flush() { }
}
// ===========================================================
// "S" variants — derive from Stream directly.
// ===========================================================
public sealed class ReadOnlyMemoryStreamS : Stream
{
private ReadOnlyMemory<byte> _buffer;
private int _position;
private bool _isOpen;
public ReadOnlyMemoryStreamS(ReadOnlyMemory<byte> source)
{
_buffer = source;
_isOpen = true;
}
public override bool CanRead => _isOpen;
public override bool CanSeek => _isOpen;
public override bool CanWrite => false;
public override long Length
{
get
{
ObjectDisposedException.ThrowIf(!_isOpen, this);
return _buffer.Length;
}
}
public override long Position
{
get { ObjectDisposedException.ThrowIf(!_isOpen, this); return _position; }
set
{
ObjectDisposedException.ThrowIf(!_isOpen, this);
ArgumentOutOfRangeException.ThrowIfNegative(value);
_position = (int)value;
}
}
public override int ReadByte()
{
ObjectDisposedException.ThrowIf(!_isOpen, this);
ReadOnlySpan<byte> span = _buffer.Span;
int pos = _position;
if ((uint)pos >= (uint)span.Length) return -1;
_position = pos + 1;
return span[pos];
}
public override int Read(byte[] buffer, int offset, int count)
{
ObjectDisposedException.ThrowIf(!_isOpen, this);
ReadOnlySpan<byte> src = _buffer.Span;
int pos = _position;
int available = src.Length - pos;
if (available <= 0) return 0;
int toCopy = Math.Min(available, count);
src.Slice(pos, toCopy).CopyTo(buffer.AsSpan(offset, toCopy));
_position = pos + toCopy;
return toCopy;
}
public override long Seek(long offset, SeekOrigin origin)
{
ObjectDisposedException.ThrowIf(!_isOpen, this);
long target = origin switch
{
SeekOrigin.Begin => offset,
SeekOrigin.Current => _position + offset,
SeekOrigin.End => _buffer.Length + offset,
_ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin))
};
ArgumentOutOfRangeException.ThrowIfNegative(target);
_position = (int)target;
return _position;
}
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
public override void Flush() { }
protected override void Dispose(bool disposing)
{
_isOpen = false;
base.Dispose(disposing);
}
}
public sealed class WritableMemoryStreamS : Stream
{
private Memory<byte> _memory;
private int _position;
private int _length;
private bool _isOpen;
public WritableMemoryStreamS(Memory<byte> buffer)
{
_memory = buffer;
_isOpen = true;
}
public override bool CanRead => _isOpen;
public override bool CanSeek => _isOpen;
public override bool CanWrite => _isOpen;
public override long Length
{
get { ObjectDisposedException.ThrowIf(!_isOpen, this); return _length; }
}
public override long Position
{
get { ObjectDisposedException.ThrowIf(!_isOpen, this); return _position; }
set
{
ObjectDisposedException.ThrowIf(!_isOpen, this);
ArgumentOutOfRangeException.ThrowIfNegative(value);
_position = (int)value;
}
}
public override int ReadByte()
{
ObjectDisposedException.ThrowIf(!_isOpen, this);
ReadOnlySpan<byte> span = _memory.Span;
int pos = _position;
if ((uint)pos < (uint)_length)
{
_position = pos + 1;
return span[pos];
}
return -1;
}
public override int Read(byte[] buffer, int offset, int count)
{
ObjectDisposedException.ThrowIf(!_isOpen, this);
int remaining = _length - _position;
if (remaining <= 0 || count == 0) return 0;
int toCopy = Math.Min(remaining, count);
_memory.Span.Slice(_position, toCopy).CopyTo(buffer.AsSpan(offset, toCopy));
_position += toCopy;
return toCopy;
}
public override void WriteByte(byte value)
{
ObjectDisposedException.ThrowIf(!_isOpen, this);
if (_position >= _memory.Length)
throw new NotSupportedException("Stream is not expandable.");
_memory.Span[_position++] = value;
if (_position > _length) _length = _position;
}
public override void Write(byte[] buffer, int offset, int count)
{
ObjectDisposedException.ThrowIf(!_isOpen, this);
if (count == 0) return;
if (_position > _memory.Length - count)
throw new NotSupportedException("Stream is not expandable.");
buffer.AsSpan(offset, count).CopyTo(_memory.Span.Slice(_position));
_position += count;
if (_position > _length) _length = _position;
}
public override long Seek(long offset, SeekOrigin origin)
{
ObjectDisposedException.ThrowIf(!_isOpen, this);
long target = origin switch
{
SeekOrigin.Begin => offset,
SeekOrigin.Current => _position + offset,
SeekOrigin.End => _length + offset,
_ => throw new ArgumentException("Invalid SeekOrigin", nameof(origin))
};
ArgumentOutOfRangeException.ThrowIfNegative(target);
_position = (int)target;
return _position;
}
public override void SetLength(long value) => throw new NotSupportedException();
public override void Flush() { }
protected override void Dispose(bool disposing)
{
_isOpen = false;
base.Dispose(disposing);
}
} |
|
awesome. that .net preview will this be in? |
* Add Stream wrappers for memory and text-based types Polyfills StringStream, ReadOnlyMemoryStream, WritableMemoryStream and ReadOnlySequenceStream from dotnet/runtime#126669. These types are approved for net11 but are not in the current net11 preview, so they are gated on `#if FeatureMemory` and remain active on net11. When a net11 SDK ships the real types, ApiBuilderTests.StreamWrapperBclDetectionTests fails with the exact change to make in each file: flip the guard to `#if FeatureMemory && !NET11_0_OR_GREATER` and add a TypeForwardedTo under `#if NET11_0_OR_GREATER`. The same steps are documented atop each source file. - ReadOnlyMemoryStream/WritableMemoryStream derive from MemoryStream and delegate to the MemoryStream(byte[],int,int,bool,bool) constructor, giving read-only / fixed-capacity semantics with GetBuffer throwing and TryGetBuffer returning false, without relying on MemoryStream internals unavailable on older TFMs. - StringStream (non-seekable, never emits a BOM) and ReadOnlySequenceStream (seekable) derive from Stream. Tests run on every framework where FeatureMemory is available; verified compiling across all 22 TFMs and passing on net11.0 and net48. * .
|
/backport to release/11.0-preview6 |
|
Started backporting to |
|
@jozkee backporting to git am output$ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch
Applying: First API proposal implementation
Using index info to reconstruct a base tree...
M src/libraries/System.Memory/ref/System.Memory.cs
M src/libraries/System.Memory/tests/System.Memory.Tests.csproj
M src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
M src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
M src/libraries/System.Runtime/ref/System.Runtime.cs
Falling back to patching base and 3-way merge...
Auto-merging src/libraries/System.Memory/ref/System.Memory.cs
Auto-merging src/libraries/System.Memory/tests/System.Memory.Tests.csproj
Auto-merging src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
Auto-merging src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
Auto-merging src/libraries/System.Runtime/ref/System.Runtime.cs
Applying: API replacements in production code
Applying: Implementation update based on latest API Review final consensus
Applying: Address PR feedback. MemoryStream base change. Spillover buffer for Convert edge case. Flush strategy update.
Applying: Address PR feedback
Applying: Address PR feedback: fix StringStream flush offset, WritableMemoryStream length/seek semantics, ReadOnlySequenceStream safety, and consolidate tests
Applying: Address PR feedback: remove redundant code, improve test coverage and structure
Using index info to reconstruct a base tree...
M src/libraries/System.Memory/src/System/Buffers/ReadOnlySequenceStream.cs
M src/libraries/System.Net.Http/src/System.Net.Http.csproj
M src/libraries/System.Private.CoreLib/src/System/IO/WritableMemoryStream.cs
M src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonXmlDataContract.cs
M src/libraries/System.Runtime/tests/System.IO.Tests/ReadOnlyMemoryStream/ReadOnlyMemoryStreamConformanceTests.cs
M src/libraries/System.Runtime/tests/System.IO.Tests/StringStream/StringStreamTests_Memory.cs
M src/libraries/System.Runtime/tests/System.IO.Tests/StringStream/StringStreamTests_String.cs
M src/libraries/System.Runtime/tests/System.IO.Tests/WritableMemoryStream/WritableMemoryStreamConformanceTests.cs
M src/libraries/System.Runtime/tests/System.IO.Tests/WritableMemoryStream/WritableMemoryStreamTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/libraries/System.Memory/src/System/Buffers/ReadOnlySequenceStream.cs
Auto-merging src/libraries/System.Net.Http/src/System.Net.Http.csproj
Auto-merging src/libraries/System.Private.CoreLib/src/System/IO/WritableMemoryStream.cs
Auto-merging src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonXmlDataContract.cs
CONFLICT (content): Merge conflict in src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonXmlDataContract.cs
Auto-merging src/libraries/System.Runtime/tests/System.IO.Tests/ReadOnlyMemoryStream/ReadOnlyMemoryStreamConformanceTests.cs
CONFLICT (content): Merge conflict in src/libraries/System.Runtime/tests/System.IO.Tests/ReadOnlyMemoryStream/ReadOnlyMemoryStreamConformanceTests.cs
Auto-merging src/libraries/System.Runtime/tests/System.IO.Tests/StringStream/StringStreamTests_Memory.cs
CONFLICT (content): Merge conflict in src/libraries/System.Runtime/tests/System.IO.Tests/StringStream/StringStreamTests_Memory.cs
Auto-merging src/libraries/System.Runtime/tests/System.IO.Tests/StringStream/StringStreamTests_String.cs
CONFLICT (content): Merge conflict in src/libraries/System.Runtime/tests/System.IO.Tests/StringStream/StringStreamTests_String.cs
Auto-merging src/libraries/System.Runtime/tests/System.IO.Tests/WritableMemoryStream/WritableMemoryStreamConformanceTests.cs
CONFLICT (content): Merge conflict in src/libraries/System.Runtime/tests/System.IO.Tests/WritableMemoryStream/WritableMemoryStreamConformanceTests.cs
Auto-merging src/libraries/System.Runtime/tests/System.IO.Tests/WritableMemoryStream/WritableMemoryStreamTests.cs
CONFLICT (content): Merge conflict in src/libraries/System.Runtime/tests/System.IO.Tests/WritableMemoryStream/WritableMemoryStreamTests.cs
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0007 Address PR feedback: remove redundant code, improve test coverage and structure
Error: The process '/usr/bin/git' failed with exit code 128 |
… types (#129811) Backport of #126669 to release/11.0-preview6. See that PR for full details. Adds new public stream wrapper APIs (`StringStream`, `ReadOnlyMemoryStream`, `WritableMemoryStream`, `ReadOnlySequenceStream`). Fixes #82801. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…562) (#563) * Align stream wrappers with the dotnet 11 implementation (follow-up to #562) StringStream and ReadOnlySequenceStream diverged from dotnet/runtime#126669 in ways that hurt performance on the cases those types exist for. Bring them in line and port the PR's tests. StringStream - Encode incrementally via a stateful Encoder (fast-path single-shot, spillover buffer for sub-scalar reads, final flush) instead of encoding the whole text into one buffer on first read. Peak memory is now bounded by the caller's read buffer rather than the full encoded length, matching the BCL. - Span-based Encoder.Convert/Encoding.GetBytes on net core 2.1 / netstandard 2.1+, array-based equivalents on older TFMs. ReadOnlySequenceStream - Track a SequencePosition cursor alongside the absolute position so Read/ReadByte advance from the current segment instead of re-slicing from the start every call (was O(segments^2) for multi-segment sequences). Seek/Position reposition the cursor forward from the current spot, walking from the start only for back jumps. - Override CopyTo/CopyToAsync to write segments directly to the destination. CopyTo is gated to net core 2.1 / netstandard 2.1+ (Stream.CopyTo(Stream, int) is not virtual on older TFMs); CopyToAsync applies everywhere. ReadOnlyMemoryStream / WritableMemoryStream are unchanged: they are already at parity for array-backed memory, and matching the BCL for native (non-array) memory would require MemoryStream internals unavailable outside CoreLib. Tests - Port the explicit unit tests from the PR (StringStream encodings, surrogate pairs, chunk boundaries, GetMaxByteCount overflow guard, memory-slice/char-array ctors; WritableMemoryStream capacity/seek/overwrite cases) into the TUnit suite, plus ReadOnlySequenceStream CopyTo/CopyToAsync coverage. Regenerated Split + assemblySize; public API surface unchanged. Verified across all 22 TFMs (Consume) and passing on net10.0 and net48. * Update Directory.Build.props
Fixes #82801
Introduces standardized stream wrappers for text and memory types as public sealed classes (
StringStream,ReadOnlyMemoryStream,WritableMemoryStream, andReadOnlySequenceStream) as approved in API review (video).What's included
StringStream(System.IO, CoreLib): non-seekable, read-only stream that encodesstringorReadOnlyMemory<char>on-the-fly. Encoding is required (no default). Never emits BOMs.ReadOnlyMemoryStream(System.IO, CoreLib): seekable, read-only stream overReadOnlyMemory<byte>WritableMemoryStream(System.IO, CoreLib): seekable, read-write stream overMemory<byte>with fixed capacityReadOnlySequenceStream(System.Buffers, System.Memory): seekable, read-only stream overReadOnlySequence<byte>System.RuntimeandSystem.MemoryReadOnlyMemoryContent,XmlPreloadedResolver,JsonXmlDataContract) to use the new typesImplementation notes
public sealedwith direct constructors: the review moved away from factory methods onStreamto avoid derived-type IntelliSense noiseStringStreamencodes directly into the caller's buffer inRead()rather than using an internal byte bufferReadOnlyMemoryStreamandWritableMemoryStreamderive fromMemoryStream.TryGetBufferreturns false andGetBufferthrows on the memory stream types (nopubliclyVisiblesupport for now)Limitations
ReadOnlyMemoryStreaminCommon/cannot be fully replaced:System.Memory.Datamulti-targetsnetstandard2.0where the new public type doesn't exist.System.Net.Http(net11.0 only) can adopt it directly.Follow-up work
IBufferWriter<byte>→Streamwrapper (separate proposal per #100434)