-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from RichardD2/main
Add .NET Standard 2.0 target.
- Loading branch information
Showing
10 changed files
with
420 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,256 @@ | ||
#if NET6_0_OR_GREATER || NETSTANDARD2_1 | ||
|
||
using System.Runtime.CompilerServices; | ||
[assembly: TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] | ||
|
||
#else | ||
|
||
using System; | ||
using System.Diagnostics; | ||
|
||
namespace System.Buffers | ||
{ | ||
/// <summary> | ||
/// Represents a heap-based, array-backed output sink into which <typeparamref name="T"/> data can be written. | ||
/// </summary> | ||
/// <typeparam name="T"></typeparam> | ||
public sealed class ArrayBufferWriter<T> : IBufferWriter<T> | ||
{ | ||
// Copy of Array.MaxLength. | ||
// Used by projects targeting .NET Framework. | ||
private const int ArrayMaxLength = 0x7FFFFFC7; | ||
|
||
private const int DefaultInitialBufferSize = 256; | ||
|
||
private T[] _buffer; | ||
private int _index; | ||
|
||
|
||
/// <summary> | ||
/// Creates an instance of an <see cref="ArrayBufferWriter{T}"/>, in which data can be written to, | ||
/// with the default initial capacity. | ||
/// </summary> | ||
public ArrayBufferWriter() | ||
{ | ||
_buffer = Array.Empty<T>(); | ||
_index = 0; | ||
} | ||
|
||
/// <summary> | ||
/// Creates an instance of an <see cref="ArrayBufferWriter{T}"/>, in which data can be written to, | ||
/// with an initial capacity specified. | ||
/// </summary> | ||
/// <param name="initialCapacity">The minimum capacity with which to initialize the underlying buffer.</param> | ||
/// <exception cref="ArgumentException"> | ||
/// Thrown when <paramref name="initialCapacity"/> is not positive (i.e. less than or equal to 0). | ||
/// </exception> | ||
public ArrayBufferWriter(int initialCapacity) | ||
{ | ||
if (initialCapacity <= 0) | ||
throw new ArgumentException(null, nameof(initialCapacity)); | ||
|
||
_buffer = new T[initialCapacity]; | ||
_index = 0; | ||
} | ||
|
||
/// <summary> | ||
/// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>. | ||
/// </summary> | ||
public ReadOnlyMemory<T> WrittenMemory => _buffer.AsMemory(0, _index); | ||
|
||
/// <summary> | ||
/// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>. | ||
/// </summary> | ||
public ReadOnlySpan<T> WrittenSpan => _buffer.AsSpan(0, _index); | ||
|
||
/// <summary> | ||
/// Returns the amount of data written to the underlying buffer so far. | ||
/// </summary> | ||
public int WrittenCount => _index; | ||
|
||
/// <summary> | ||
/// Returns the total amount of space within the underlying buffer. | ||
/// </summary> | ||
public int Capacity => _buffer.Length; | ||
|
||
/// <summary> | ||
/// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow. | ||
/// </summary> | ||
public int FreeCapacity => _buffer.Length - _index; | ||
|
||
/// <summary> | ||
/// Clears the data written to the underlying buffer. | ||
/// </summary> | ||
/// <remarks> | ||
/// <para> | ||
/// You must reset or clear the <see cref="ArrayBufferWriter{T}"/> before trying to re-use it. | ||
/// </para> | ||
/// <para> | ||
/// The <see cref="ResetWrittenCount"/> method is faster since it only sets to zero the writer's index | ||
/// while the <see cref="Clear"/> method additionally zeroes the content of the underlying buffer. | ||
/// </para> | ||
/// </remarks> | ||
/// <seealso cref="ResetWrittenCount"/> | ||
public void Clear() | ||
{ | ||
Debug.Assert(_buffer.Length >= _index); | ||
_buffer.AsSpan(0, _index).Clear(); | ||
_index = 0; | ||
} | ||
|
||
/// <summary> | ||
/// Resets the data written to the underlying buffer without zeroing its content. | ||
/// </summary> | ||
/// <remarks> | ||
/// <para> | ||
/// You must reset or clear the <see cref="ArrayBufferWriter{T}"/> before trying to re-use it. | ||
/// </para> | ||
/// <para> | ||
/// If you reset the writer using the <see cref="ResetWrittenCount"/> method, the underlying buffer will not be cleared. | ||
/// </para> | ||
/// </remarks> | ||
/// <seealso cref="Clear"/> | ||
public void ResetWrittenCount() => _index = 0; | ||
|
||
/// <summary> | ||
/// Notifies <see cref="IBufferWriter{T}"/> that <paramref name="count"/> amount of data was written to the output <see cref="Span{T}"/>/<see cref="Memory{T}"/> | ||
/// </summary> | ||
/// <exception cref="ArgumentException"> | ||
/// Thrown when <paramref name="count"/> is negative. | ||
/// </exception> | ||
/// <exception cref="InvalidOperationException"> | ||
/// Thrown when attempting to advance past the end of the underlying buffer. | ||
/// </exception> | ||
/// <remarks> | ||
/// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. | ||
/// </remarks> | ||
public void Advance(int count) | ||
{ | ||
if (count < 0) | ||
throw new ArgumentException(null, nameof(count)); | ||
|
||
if (_index > _buffer.Length - count) | ||
ThrowInvalidOperationException_AdvancedTooFar(_buffer.Length); | ||
|
||
_index += count; | ||
} | ||
|
||
/// <summary> | ||
/// Returns a <see cref="Memory{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>). | ||
/// If no <paramref name="sizeHint"/> is provided (or it's equal to <code>0</code>), some non-empty buffer is returned. | ||
/// </summary> | ||
/// <exception cref="ArgumentException"> | ||
/// Thrown when <paramref name="sizeHint"/> is negative. | ||
/// </exception> | ||
/// <remarks> | ||
/// <para> | ||
/// This will never return an empty <see cref="Memory{T}"/>. | ||
/// </para> | ||
/// <para> | ||
/// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. | ||
/// </para> | ||
/// <para> | ||
/// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. | ||
/// </para> | ||
/// <para> | ||
/// If you reset the writer using the <see cref="ResetWrittenCount"/> method, this method may return a non-cleared <see cref="Memory{T}"/>. | ||
/// </para> | ||
/// <para> | ||
/// If you clear the writer using the <see cref="Clear"/> method, this method will return a <see cref="Memory{T}"/> with its content zeroed. | ||
/// </para> | ||
/// </remarks> | ||
public Memory<T> GetMemory(int sizeHint = 0) | ||
{ | ||
CheckAndResizeBuffer(sizeHint); | ||
Debug.Assert(_buffer.Length > _index); | ||
return _buffer.AsMemory(_index); | ||
} | ||
|
||
/// <summary> | ||
/// Returns a <see cref="Span{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>). | ||
/// If no <paramref name="sizeHint"/> is provided (or it's equal to <code>0</code>), some non-empty buffer is returned. | ||
/// </summary> | ||
/// <exception cref="ArgumentException"> | ||
/// Thrown when <paramref name="sizeHint"/> is negative. | ||
/// </exception> | ||
/// <remarks> | ||
/// <para> | ||
/// This will never return an empty <see cref="Span{T}"/>. | ||
/// </para> | ||
/// <para> | ||
/// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. | ||
/// </para> | ||
/// <para> | ||
/// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. | ||
/// </para> | ||
/// <para> | ||
/// If you reset the writer using the <see cref="ResetWrittenCount"/> method, this method may return a non-cleared <see cref="Span{T}"/>. | ||
/// </para> | ||
/// <para> | ||
/// If you clear the writer using the <see cref="Clear"/> method, this method will return a <see cref="Span{T}"/> with its content zeroed. | ||
/// </para> | ||
/// </remarks> | ||
public Span<T> GetSpan(int sizeHint = 0) | ||
{ | ||
CheckAndResizeBuffer(sizeHint); | ||
Debug.Assert(_buffer.Length > _index); | ||
return _buffer.AsSpan(_index); | ||
} | ||
|
||
private void CheckAndResizeBuffer(int sizeHint) | ||
{ | ||
if (sizeHint < 0) | ||
throw new ArgumentException(nameof(sizeHint)); | ||
|
||
if (sizeHint == 0) | ||
{ | ||
sizeHint = 1; | ||
} | ||
|
||
if (sizeHint > FreeCapacity) | ||
{ | ||
int currentLength = _buffer.Length; | ||
|
||
// Attempt to grow by the larger of the sizeHint and double the current size. | ||
int growBy = Math.Max(sizeHint, currentLength); | ||
|
||
if (currentLength == 0) | ||
{ | ||
growBy = Math.Max(growBy, DefaultInitialBufferSize); | ||
} | ||
|
||
int newSize = currentLength + growBy; | ||
|
||
if ((uint)newSize > int.MaxValue) | ||
{ | ||
// Attempt to grow to ArrayMaxLength. | ||
uint needed = (uint)(currentLength - FreeCapacity + sizeHint); | ||
Debug.Assert(needed > currentLength); | ||
|
||
if (needed > ArrayMaxLength) | ||
{ | ||
ThrowOutOfMemoryException(needed); | ||
} | ||
|
||
newSize = ArrayMaxLength; | ||
} | ||
|
||
Array.Resize(ref _buffer, newSize); | ||
} | ||
|
||
Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint); | ||
} | ||
|
||
private static void ThrowInvalidOperationException_AdvancedTooFar(int capacity) | ||
{ | ||
throw new InvalidOperationException(); | ||
} | ||
|
||
private static void ThrowOutOfMemoryException(uint capacity) | ||
{ | ||
throw new OutOfMemoryException(); | ||
} | ||
} | ||
} | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#if NETSTANDARD2_0 | ||
|
||
using System; | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
|
||
namespace Utf8StringInterpolation | ||
{ | ||
internal static partial class Shims | ||
{ | ||
public static string GetString(this Encoding encoding, scoped ReadOnlySpan<byte> bytes) | ||
{ | ||
if (bytes.IsEmpty) return string.Empty; | ||
|
||
unsafe | ||
{ | ||
fixed (byte* pB = &MemoryMarshal.GetReference(bytes)) | ||
{ | ||
return encoding.GetString(pB, bytes.Length); | ||
} | ||
} | ||
} | ||
|
||
public static int GetByteCount(this Encoding encoding, scoped ReadOnlySpan<char> chars) | ||
{ | ||
unsafe | ||
{ | ||
fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) | ||
{ | ||
return encoding.GetByteCount(charsPtr, chars.Length); | ||
} | ||
} | ||
} | ||
|
||
public static int GetBytes(this Encoding encoding, scoped ReadOnlySpan<char> chars, scoped Span<byte> bytes) | ||
{ | ||
unsafe | ||
{ | ||
fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) | ||
fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) | ||
{ | ||
return encoding.GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length); | ||
} | ||
} | ||
} | ||
|
||
private static bool TryFormat(this DateTime value, scoped Span<char> destination, out int charsWritten, string? format, IFormatProvider? formatProvider) | ||
{ | ||
string s = value.ToString(format, formatProvider); | ||
if (s.Length > destination.Length) | ||
{ | ||
charsWritten = 0; | ||
return false; | ||
} | ||
|
||
s.AsSpan().CopyTo(destination); | ||
charsWritten = s.Length; | ||
return true; | ||
} | ||
|
||
private static bool TryFormat(this DateTimeOffset value, scoped Span<char> destination, out int charsWritten, string? format, IFormatProvider? formatProvider) | ||
{ | ||
string s = value.ToString(format, formatProvider); | ||
if (s.Length > destination.Length) | ||
{ | ||
charsWritten = 0; | ||
return false; | ||
} | ||
|
||
s.AsSpan().CopyTo(destination); | ||
charsWritten = s.Length; | ||
return true; | ||
} | ||
|
||
private static bool TryFormat(this TimeSpan value, scoped Span<char> destination, out int charsWritten, string? format, IFormatProvider? formatProvider) | ||
{ | ||
string s = value.ToString(format, formatProvider); | ||
if (s.Length > destination.Length) | ||
{ | ||
charsWritten = 0; | ||
return false; | ||
} | ||
|
||
s.AsSpan().CopyTo(destination); | ||
charsWritten = s.Length; | ||
return true; | ||
} | ||
} | ||
} | ||
|
||
#endif |
Oops, something went wrong.