// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. /*============================================================ ** ** ** ** Purpose: Convenient wrapper for an array, an offset, and ** a count. Ideally used in streams & collections. ** Net Classes will consume an array of these. ** ** ===========================================================*/ using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Diagnostics.Contracts; namespace System { // Note: users should make sure they copy the fields out of an ArraySegment onto their stack // then validate that the fields describe valid bounds within the array. This must be done // because assignments to value types are not atomic, and also because one thread reading // three fields from an ArraySegment may not see the same ArraySegment from one call to another // (ie, users could assign a new value to the old location). [Serializable] public struct ArraySegment : IList, IReadOnlyList { private T[] _array; private int _offset; private int _count; public ArraySegment(T[] array) { if (array == null) throw new ArgumentNullException("array"); Contract.EndContractBlock(); _array = array; _offset = 0; _count = array.Length; } public ArraySegment(T[] array, int offset, int count) { if (array == null) throw new ArgumentNullException("array"); if (offset < 0) throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); if (count < 0) throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); if (array.Length - offset < count) throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); Contract.EndContractBlock(); _array = array; _offset = offset; _count = count; } public T[] Array { get { Contract.Assert( (null == _array && 0 == _offset && 0 == _count) || (null != _array && _offset >= 0 && _count >= 0 && _offset + _count <= _array.Length), "ArraySegment is invalid"); return _array; } } public int Offset { get { // Since copying value types is not atomic & callers cannot atomically // read all three fields, we cannot guarantee that Offset is within // the bounds of Array. That is our intent, but let's not specify // it as a postcondition - force callers to re-verify this themselves // after reading each field out of an ArraySegment into their stack. Contract.Ensures(Contract.Result() >= 0); Contract.Assert( (null == _array && 0 == _offset && 0 == _count) || (null != _array && _offset >= 0 && _count >= 0 && _offset + _count <= _array.Length), "ArraySegment is invalid"); return _offset; } } public int Count { get { // Since copying value types is not atomic & callers cannot atomically // read all three fields, we cannot guarantee that Count is within // the bounds of Array. That's our intent, but let's not specify // it as a postcondition - force callers to re-verify this themselves // after reading each field out of an ArraySegment into their stack. Contract.Ensures(Contract.Result() >= 0); Contract.Assert( (null == _array && 0 == _offset && 0 == _count) || (null != _array && _offset >= 0 && _count >= 0 && _offset + _count <= _array.Length), "ArraySegment is invalid"); return _count; } } public override int GetHashCode() { return null == _array ? 0 : _array.GetHashCode() ^ _offset ^ _count; } public override bool Equals(Object obj) { if (obj is ArraySegment) return Equals((ArraySegment)obj); else return false; } public bool Equals(ArraySegment obj) { return obj._array == _array && obj._offset == _offset && obj._count == _count; } public static bool operator ==(ArraySegment a, ArraySegment b) { return a.Equals(b); } public static bool operator !=(ArraySegment a, ArraySegment b) { return !(a == b); } #region IList T IList.this[int index] { get { if (_array == null) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullArray")); if (index < 0 || index >= _count) throw new ArgumentOutOfRangeException("index"); Contract.EndContractBlock(); return _array[_offset + index]; } set { if (_array == null) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullArray")); if (index < 0 || index >= _count) throw new ArgumentOutOfRangeException("index"); Contract.EndContractBlock(); _array[_offset + index] = value; } } int IList.IndexOf(T item) { if (_array == null) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullArray")); Contract.EndContractBlock(); int index = System.Array.IndexOf(_array, item, _offset, _count); Contract.Assert(index == -1 || (index >= _offset && index < _offset + _count)); return index >= 0 ? index - _offset : -1; } void IList.Insert(int index, T item) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } #endregion #region IReadOnlyList T IReadOnlyList.this[int index] { get { if (_array == null) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullArray")); if (index < 0 || index >= _count) throw new ArgumentOutOfRangeException("index"); Contract.EndContractBlock(); return _array[_offset + index]; } } #endregion IReadOnlyList #region ICollection bool ICollection.IsReadOnly { get { // the indexer setter does not throw an exception although IsReadOnly is true. // This is to match the behavior of arrays. return true; } } void ICollection.Add(T item) { throw new NotSupportedException(); } void ICollection.Clear() { throw new NotSupportedException(); } bool ICollection.Contains(T item) { if (_array == null) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullArray")); Contract.EndContractBlock(); int index = System.Array.IndexOf(_array, item, _offset, _count); Contract.Assert(index == -1 || (index >= _offset && index < _offset + _count)); return index >= 0; } void ICollection.CopyTo(T[] array, int arrayIndex) { if (_array == null) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullArray")); Contract.EndContractBlock(); System.Array.Copy(_array, _offset, array, arrayIndex, _count); } bool ICollection.Remove(T item) { throw new NotSupportedException(); } #endregion #region IEnumerable IEnumerator IEnumerable.GetEnumerator() { if (_array == null) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullArray")); Contract.EndContractBlock(); return new ArraySegmentEnumerator(this); } #endregion #region IEnumerable IEnumerator IEnumerable.GetEnumerator() { if (_array == null) throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullArray")); Contract.EndContractBlock(); return new ArraySegmentEnumerator(this); } #endregion [Serializable] private sealed class ArraySegmentEnumerator : IEnumerator { private T[] _array; private int _start; private int _end; private int _current; internal ArraySegmentEnumerator(ArraySegment arraySegment) { Contract.Requires(arraySegment.Array != null); Contract.Requires(arraySegment.Offset >= 0); Contract.Requires(arraySegment.Count >= 0); Contract.Requires(arraySegment.Offset + arraySegment.Count <= arraySegment.Array.Length); _array = arraySegment._array; _start = arraySegment._offset; _end = _start + arraySegment._count; _current = _start - 1; } public bool MoveNext() { if (_current < _end) { _current++; return (_current < _end); } return false; } public T Current { get { if (_current < _start) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted)); if (_current >= _end) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumEnded)); return _array[_current]; } } object IEnumerator.Current { get { return Current; } } void IEnumerator.Reset() { _current = _start - 1; } public void Dispose() { } } } }