// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace CommunityToolkit.HighPerformance.Streams; /// /// A implementation wrapping a or instance. /// /// The type of source to use for the underlying data. /// /// This type is not marked as so that it can be inherited by /// , which adds the support for /// the wrapped buffer. We're not worried about the performance penalty here caused by the JIT /// not being able to resolve the instruction, as this type is /// only exposed as a anyway, so the generated code would be the same. /// internal partial class MemoryStream : Stream where TSource : struct, ISpanOwner { /// /// Indicates whether can be written to. /// private readonly bool isReadOnly; /// /// The instance currently in use. /// private TSource source; /// /// The current position within . /// private int position; /// /// Indicates whether or not the current instance has been disposed /// private bool disposed; /// /// Initializes a new instance of the class. /// /// The input instance to use. /// Indicates whether can be written to. public MemoryStream(TSource source, bool isReadOnly) { this.source = source; this.isReadOnly = isReadOnly; } /// public sealed override bool CanRead { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => !this.disposed; } /// public sealed override bool CanSeek { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => !this.disposed; } /// public sealed override bool CanWrite { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => !this.isReadOnly && !this.disposed; } /// public sealed override long Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { MemoryStream.ValidateDisposed(this.disposed); return this.source.Length; } } /// public sealed override long Position { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { MemoryStream.ValidateDisposed(this.disposed); return this.position; } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { MemoryStream.ValidateDisposed(this.disposed); MemoryStream.ValidatePosition(value, this.source.Length); this.position = unchecked((int)value); } } /// public sealed override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } try { CopyTo(destination, bufferSize); return Task.CompletedTask; } catch (OperationCanceledException e) { return Task.FromCanceled(e.CancellationToken); } catch (Exception e) { return Task.FromException(e); } } /// public sealed override void Flush() { } /// public sealed override Task FlushAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } return Task.CompletedTask; } /// public sealed override Task ReadAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } try { int result = Read(buffer, offset, count); return Task.FromResult(result); } catch (OperationCanceledException e) { return Task.FromCanceled(e.CancellationToken); } catch (Exception e) { return Task.FromException(e); } } /// public sealed override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } try { Write(buffer, offset, count); return Task.CompletedTask; } catch (OperationCanceledException e) { return Task.FromCanceled(e.CancellationToken); } catch (Exception e) { return Task.FromException(e); } } /// public sealed override long Seek(long offset, SeekOrigin origin) { MemoryStream.ValidateDisposed(this.disposed); long index = origin switch { SeekOrigin.Begin => offset, SeekOrigin.Current => this.position + offset, SeekOrigin.End => this.source.Length + offset, _ => MemoryStream.ThrowArgumentExceptionForSeekOrigin() }; MemoryStream.ValidatePosition(index, this.source.Length); this.position = unchecked((int)index); return index; } /// public sealed override void SetLength(long value) { throw MemoryStream.GetNotSupportedException(); } /// public sealed override int Read(byte[]? buffer, int offset, int count) { MemoryStream.ValidateDisposed(this.disposed); MemoryStream.ValidateBuffer(buffer, offset, count); int bytesAvailable = this.source.Length - this.position; int bytesCopied = Math.Min(bytesAvailable, count); Span source = this.source.Span.Slice(this.position, bytesCopied); Span destination = buffer.AsSpan(offset, bytesCopied); source.CopyTo(destination); this.position += bytesCopied; return bytesCopied; } /// public sealed override int ReadByte() { MemoryStream.ValidateDisposed(this.disposed); if (this.position == this.source.Length) { return -1; } return this.source.Span[this.position++]; } /// public sealed override void Write(byte[]? buffer, int offset, int count) { MemoryStream.ValidateDisposed(this.disposed); MemoryStream.ValidateCanWrite(CanWrite); MemoryStream.ValidateBuffer(buffer, offset, count); Span source = buffer.AsSpan(offset, count); Span destination = this.source.Span.Slice(this.position); if (!source.TryCopyTo(destination)) { MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite(); } this.position += source.Length; } /// public sealed override void WriteByte(byte value) { MemoryStream.ValidateDisposed(this.disposed); MemoryStream.ValidateCanWrite(CanWrite); if (this.position == this.source.Length) { MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite(); } this.source.Span[this.position++] = value; } /// protected override void Dispose(bool disposing) { if (this.disposed) { return; } this.disposed = true; this.source = default; } }