|
// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
/*============================================================
**
** Class: MemoryStream
**
** <OWNER>Microsoft</OWNER>
**
**
** Purpose: A Stream whose backing store is memory. Great
** for temporary storage without creating a temp file. Also
** lets users expose a byte[] as a stream.
**
**
===========================================================*/
using System;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Diagnostics.Contracts;
#if FEATURE_ASYNC_IO
using System.Threading;
using System.Threading.Tasks;
using System.Security.Permissions;
#endif
namespace System.IO {
// A MemoryStream represents a Stream in memory (ie, it has no backing store).
// This stream may reduce the need for temporary buffers and files in
// an application.
//
// There are two ways to create a MemoryStream. You can initialize one
// from an unsigned byte array, or you can create an empty one. Empty
// memory streams are resizable, while ones created with a byte array provide
// a stream "view" of the data.
[Serializable]
[ComVisible(true)]
public class MemoryStream : Stream
{
private byte[] _buffer; // Either allocated internally or externally.
private int _origin; // For user-provided arrays, start at this origin
private int _position; // read/write head.
[ContractPublicPropertyName("Length")]
private int _length; // Number of bytes within the memory stream
private int _capacity; // length of usable portion of buffer for stream
// Note that _capacity == _buffer.Length for non-user-provided byte[]'s
private bool _expandable; // User-provided buffers aren't expandable.
private bool _writable; // Can user write to this stream?
private bool _exposable; // Whether the array can be returned to the user.
private bool _isOpen; // Is this stream open or closed?
#if FEATURE_ASYNC_IO
[NonSerialized]
private Task<int> _lastReadTask; // The last successful task returned from ReadAsync
#endif
// <
private const int MemStreamMaxLength = Int32.MaxValue;
public MemoryStream()
: this(0) {
}
public MemoryStream(int capacity) {
if (capacity < 0) {
throw new ArgumentOutOfRangeException("capacity", Environment.GetResourceString("ArgumentOutOfRange_NegativeCapacity"));
}
Contract.EndContractBlock();
_buffer = new byte[capacity];
_capacity = capacity;
_expandable = true;
_writable = true;
_exposable = true;
_origin = 0; // Must be 0 for byte[]'s created by MemoryStream
_isOpen = true;
}
public MemoryStream(byte[] buffer)
: this(buffer, true) {
}
public MemoryStream(byte[] buffer, bool writable) {
if (buffer == null) throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
Contract.EndContractBlock();
_buffer = buffer;
_length = _capacity = buffer.Length;
_writable = writable;
_exposable = false;
_origin = 0;
_isOpen = true;
}
public MemoryStream(byte[] buffer, int index, int count)
: this(buffer, index, count, true, false) {
}
public MemoryStream(byte[] buffer, int index, int count, bool writable)
: this(buffer, index, count, writable, false) {
}
public MemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible) {
if (buffer==null)
throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
if (index < 0)
throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (count < 0)
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (buffer.Length - index < count)
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
Contract.EndContractBlock();
_buffer = buffer;
_origin = _position = index;
_length = _capacity = index + count;
_writable = writable;
_exposable = publiclyVisible; // Can TryGetBuffer/GetBuffer return the array?
_expandable = false;
_isOpen = true;
}
public override bool CanRead {
[Pure]
get { return _isOpen; }
}
public override bool CanSeek {
[Pure]
get { return _isOpen; }
}
public override bool CanWrite {
[Pure]
get { return _writable; }
}
private void EnsureWriteable() {
if (!CanWrite) __Error.WriteNotSupported();
}
protected override void Dispose(bool disposing)
{
try {
if (disposing) {
_isOpen = false;
_writable = false;
_expandable = false;
// Don't set buffer to null - allow TryGetBuffer, GetBuffer & ToArray to work.
#if FEATURE_ASYNC_IO
_lastReadTask = null;
#endif
}
}
finally {
// Call base.Close() to cleanup async IO resources
base.Dispose(disposing);
}
}
// returns a bool saying whether we allocated a new array.
private bool EnsureCapacity(int value) {
// Check for overflow
if (value < 0)
throw new IOException(Environment.GetResourceString("IO.IO_StreamTooLong"));
if (value > _capacity) {
int newCapacity = value;
if (newCapacity < 256)
newCapacity = 256;
// We are ok with this overflowing since the next statement will deal
// with the cases where _capacity*2 overflows.
if (newCapacity < _capacity * 2)
newCapacity = _capacity * 2;
// We want to expand the array up to Array.MaxArrayLengthOneDimensional
// And we want to give the user the value that they asked for
if ((uint)(_capacity * 2) > Array.MaxByteArrayLength)
newCapacity = value > Array.MaxByteArrayLength ? value : Array.MaxByteArrayLength;
Capacity = newCapacity;
return true;
}
return false;
}
public override void Flush() {
}
#if FEATURE_ASYNC_IO
[HostProtection(ExternalThreading=true)]
[ComVisible(false)]
public override Task FlushAsync(CancellationToken cancellationToken) {
if (cancellationToken.IsCancellationRequested)
return Task.FromCancellation(cancellationToken);
try {
Flush();
return Task.CompletedTask;
} catch(Exception ex) {
return Task.FromException(ex);
}
}
#endif // FEATURE_ASYNC_IO
public virtual byte[] GetBuffer() {
if (!_exposable)
throw new UnauthorizedAccessException(Environment.GetResourceString("UnauthorizedAccess_MemStreamBuffer"));
return _buffer;
}
public virtual bool TryGetBuffer(out ArraySegment<byte> buffer) {
if (!_exposable) {
buffer = default(ArraySegment<byte>);
return false;
}
buffer = new ArraySegment<byte>(_buffer, offset:_origin, count:(_length - _origin));
return true;
}
// -------------- PERF: Internal functions for fast direct access of MemoryStream buffer (cf. BinaryReader for usage) ---------------
// PERF: Internal sibling of GetBuffer, always returns a buffer (cf. GetBuffer())
internal byte[] InternalGetBuffer() {
return _buffer;
}
// PERF: Get origin and length - used in ResourceWriter.
[FriendAccessAllowed]
internal void InternalGetOriginAndLength(out int origin, out int length)
{
if (!_isOpen) __Error.StreamIsClosed();
origin = _origin;
length = _length;
}
// PERF: True cursor position, we don't need _origin for direct access
internal int InternalGetPosition() {
if (!_isOpen) __Error.StreamIsClosed();
return _position;
}
// PERF: Takes out Int32 as fast as possible
internal int InternalReadInt32() {
if (!_isOpen)
__Error.StreamIsClosed();
int pos = (_position += 4); // use temp to avoid ----
if (pos > _length)
{
_position = _length;
__Error.EndOfFile();
}
return (int)(_buffer[pos-4] | _buffer[pos-3] << 8 | _buffer[pos-2] << 16 | _buffer[pos-1] << 24);
}
// PERF: Get actual length of bytes available for read; do sanity checks; shift position - i.e. everything except actual copying bytes
internal int InternalEmulateRead(int count) {
if (!_isOpen) __Error.StreamIsClosed();
int n = _length - _position;
if (n > count) n = count;
if (n < 0) n = 0;
Contract.Assert(_position + n >= 0, "_position + n >= 0"); // len is less than 2^31 -1.
_position += n;
return n;
}
// Gets & sets the capacity (number of bytes allocated) for this stream.
// The capacity cannot be set to a value less than the current length
// of the stream.
//
public virtual int Capacity {
get {
if (!_isOpen) __Error.StreamIsClosed();
return _capacity - _origin;
}
set {
// Only update the capacity if the MS is expandable and the value is different than the current capacity.
// Special behavior if the MS isn't expandable: we don't throw if value is the same as the current capacity
if (value < Length) throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_SmallCapacity"));
Contract.Ensures(_capacity - _origin == value);
Contract.EndContractBlock();
if (!_isOpen) __Error.StreamIsClosed();
if (!_expandable && (value != Capacity)) __Error.MemoryStreamNotExpandable();
// MemoryStream has this invariant: _origin > 0 => !expandable (see ctors)
if (_expandable && value != _capacity) {
if (value > 0) {
byte[] newBuffer = new byte[value];
if (_length > 0) Buffer.InternalBlockCopy(_buffer, 0, newBuffer, 0, _length);
_buffer = newBuffer;
}
else {
_buffer = null;
}
_capacity = value;
}
}
}
public override long Length {
get {
if (!_isOpen) __Error.StreamIsClosed();
return _length - _origin;
}
}
public override long Position {
get {
if (!_isOpen) __Error.StreamIsClosed();
return _position - _origin;
}
set {
if (value < 0)
throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
Contract.Ensures(Position == value);
Contract.EndContractBlock();
if (!_isOpen) __Error.StreamIsClosed();
if (value > MemStreamMaxLength)
throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_StreamLength"));
_position = _origin + (int)value;
}
}
public override int Read([In, Out] byte[] buffer, int offset, int count) {
if (buffer==null)
throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (count < 0)
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (buffer.Length - offset < count)
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
Contract.EndContractBlock();
if (!_isOpen) __Error.StreamIsClosed();
int n = _length - _position;
if (n > count) n = count;
if (n <= 0)
return 0;
Contract.Assert(_position + n >= 0, "_position + n >= 0"); // len is less than 2^31 -1.
if (n <= 8)
{
int byteCount = n;
while (--byteCount >= 0)
buffer[offset + byteCount] = _buffer[_position + byteCount];
}
else
Buffer.InternalBlockCopy(_buffer, _position, buffer, offset, n);
_position += n;
return n;
}
#if FEATURE_ASYNC_IO
[HostProtection(ExternalThreading = true)]
[ComVisible(false)]
public override Task<int> ReadAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (buffer==null)
throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (count < 0)
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (buffer.Length - offset < count)
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
Contract.EndContractBlock(); // contract validation copied from Read(...)
// If cancellation was requested, bail early
if (cancellationToken.IsCancellationRequested)
return Task.FromCancellation<int>(cancellationToken);
try
{
int n = Read(buffer, offset, count);
var t = _lastReadTask;
Contract.Assert(t == null || t.Status == TaskStatus.RanToCompletion,
"Expected that a stored last task completed successfully");
return (t != null && t.Result == n) ? t : (_lastReadTask = Task.FromResult<int>(n));
}
catch (OperationCanceledException oce)
{
return Task.FromCancellation<int>(oce);
}
catch (Exception exception)
{
return Task.FromException<int>(exception);
}
}
#endif //FEATURE_ASYNC_IO
public override int ReadByte() {
if (!_isOpen) __Error.StreamIsClosed();
if (_position >= _length) return -1;
return _buffer[_position++];
}
#if FEATURE_ASYNC_IO
public override Task CopyToAsync(Stream destination, Int32 bufferSize, CancellationToken cancellationToken) {
// This implementation offers beter performance compared to the base class version.
// The parameter checks must be in sync with the base version:
if (destination == null)
throw new ArgumentNullException("destination");
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
if (!CanRead && !CanWrite)
throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_StreamClosed"));
if (!destination.CanRead && !destination.CanWrite)
throw new ObjectDisposedException("destination", Environment.GetResourceString("ObjectDisposed_StreamClosed"));
if (!CanRead)
throw new NotSupportedException(Environment.GetResourceString("NotSupported_UnreadableStream"));
if (!destination.CanWrite)
throw new NotSupportedException(Environment.GetResourceString("NotSupported_UnwritableStream"));
Contract.EndContractBlock();
// If we have been inherited into a subclass, the following implementation could be incorrect
// since it does not call through to Read() or Write() which a subclass might have overriden.
// To be safe we will only use this implementation in cases where we know it is safe to do so,
// and delegate to our base class (which will call into Read/Write) when we are not sure.
if (this.GetType() != typeof(MemoryStream))
return base.CopyToAsync(destination, bufferSize, cancellationToken);
// If cancelled - return fast:
if (cancellationToken.IsCancellationRequested)
return Task.FromCancellation(cancellationToken);
// Avoid copying data from this buffer into a temp buffer:
// (require that InternalEmulateRead does not throw,
// otherwise it needs to be wrapped into try-catch-Task.FromException like memStrDest.Write below)
Int32 pos = _position;
Int32 n = InternalEmulateRead(_length - _position);
// If destination is not a memory stream, write there asynchronously:
MemoryStream memStrDest = destination as MemoryStream;
if (memStrDest == null)
return destination.WriteAsync(_buffer, pos, n, cancellationToken);
try {
// If destination is a MemoryStream, CopyTo synchronously:
memStrDest.Write(_buffer, pos, n);
return Task.CompletedTask;
} catch(Exception ex) {
return Task.FromException(ex);
}
}
#endif //FEATURE_ASYNC_IO
public override long Seek(long offset, SeekOrigin loc) {
if (!_isOpen) __Error.StreamIsClosed();
if (offset > MemStreamMaxLength)
throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_StreamLength"));
switch(loc) {
case SeekOrigin.Begin: {
int tempPosition = unchecked(_origin + (int)offset);
if (offset < 0 || tempPosition < _origin)
throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin"));
_position = tempPosition;
break;
}
case SeekOrigin.Current: {
int tempPosition = unchecked(_position + (int)offset);
if (unchecked(_position + offset) < _origin || tempPosition < _origin)
throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin"));
_position = tempPosition;
break;
}
case SeekOrigin.End: {
int tempPosition = unchecked(_length + (int)offset);
if ( unchecked(_length + offset) < _origin || tempPosition < _origin )
throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin"));
_position = tempPosition;
break;
}
default:
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidSeekOrigin"));
}
Contract.Assert(_position >= 0, "_position >= 0");
return _position;
}
// Sets the length of the stream to a given value. The new
// value must be nonnegative and less than the space remaining in
// the array, Int32.MaxValue - origin
// Origin is 0 in all cases other than a MemoryStream created on
// top of an existing array and a specific starting offset was passed
// into the MemoryStream constructor. The upper bounds prevents any
// situations where a stream may be created on top of an array then
// the stream is made longer than the maximum possible length of the
// array (Int32.MaxValue).
//
public override void SetLength(long value) {
if (value < 0 || value > Int32.MaxValue) {
throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_StreamLength"));
}
Contract.Ensures(_length - _origin == value);
Contract.EndContractBlock();
EnsureWriteable();
// Origin wasn't publicly exposed above.
Contract.Assert(MemStreamMaxLength == Int32.MaxValue); // Check parameter validation logic in this method if this fails.
if (value > (Int32.MaxValue - _origin)) {
throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_StreamLength"));
}
int newLength = _origin + (int)value;
bool allocatedNewArray = EnsureCapacity(newLength);
if (!allocatedNewArray && newLength > _length)
Array.Clear(_buffer, _length, newLength - _length);
_length = newLength;
if (_position > newLength) _position = newLength;
}
public virtual byte[] ToArray() {
BCLDebug.Perf(_exposable, "MemoryStream::GetBuffer will let you avoid a copy.");
byte[] copy = new byte[_length - _origin];
Buffer.InternalBlockCopy(_buffer, _origin, copy, 0, _length - _origin);
return copy;
}
public override void Write(byte[] buffer, int offset, int count) {
if (buffer==null)
throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (count < 0)
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (buffer.Length - offset < count)
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
Contract.EndContractBlock();
if (!_isOpen) __Error.StreamIsClosed();
EnsureWriteable();
int i = _position + count;
// Check for overflow
if (i < 0)
throw new IOException(Environment.GetResourceString("IO.IO_StreamTooLong"));
if (i > _length) {
bool mustZero = _position > _length;
if (i > _capacity) {
bool allocatedNewArray = EnsureCapacity(i);
if (allocatedNewArray)
mustZero = false;
}
if (mustZero)
Array.Clear(_buffer, _length, i - _length);
_length = i;
}
if ((count <= 8) && (buffer != _buffer))
{
int byteCount = count;
while (--byteCount >= 0)
_buffer[_position + byteCount] = buffer[offset + byteCount];
}
else
Buffer.InternalBlockCopy(buffer, offset, _buffer, _position, count);
_position = i;
}
#if FEATURE_ASYNC_IO
[HostProtection(ExternalThreading = true)]
[ComVisible(false)]
public override Task WriteAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (buffer == null)
throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (count < 0)
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (buffer.Length - offset < count)
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
Contract.EndContractBlock(); // contract validation copied from Write(...)
// If cancellation is already requested, bail early
if (cancellationToken.IsCancellationRequested)
return Task.FromCancellation(cancellationToken);
try
{
Write(buffer, offset, count);
return Task.CompletedTask;
}
catch (OperationCanceledException oce)
{
return Task.FromCancellation<VoidTaskResult>(oce);
}
catch (Exception exception)
{
return Task.FromException(exception);
}
}
#endif // FEATURE_ASYNC_IO
public override void WriteByte(byte value) {
if (!_isOpen) __Error.StreamIsClosed();
EnsureWriteable();
if (_position >= _length) {
int newLength = _position + 1;
bool mustZero = _position > _length;
if (newLength >= _capacity) {
bool allocatedNewArray = EnsureCapacity(newLength);
if (allocatedNewArray)
mustZero = false;
}
if (mustZero)
Array.Clear(_buffer, _length, _position - _length);
_length = newLength;
}
_buffer[_position++] = value;
}
// Writes this MemoryStream to another stream.
public virtual void WriteTo(Stream stream) {
if (stream==null)
throw new ArgumentNullException("stream", Environment.GetResourceString("ArgumentNull_Stream"));
Contract.EndContractBlock();
if (!_isOpen) __Error.StreamIsClosed();
stream.Write(_buffer, _origin, _length - _origin);
}
#if CONTRACTS_FULL
[ContractInvariantMethod]
private void ObjectInvariantMS() {
Contract.Invariant(_origin >= 0);
Contract.Invariant(_origin <= _position);
Contract.Invariant(_length <= _capacity);
// equivalent to _origin > 0 => !expandable, and using fact that _origin is non-negative.
Contract.Invariant(_origin == 0 || !_expandable);
}
#endif
}
}
|