// 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. /*============================================================ ** ** ** ** 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.Diagnostics; 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 { // Do not replace the array allocation with Array.Empty. We don't want to have the overhead of // instantiating another generic type in addition to ArraySegment for new type parameters. public static ArraySegment Empty { get; } = new ArraySegment(new T[0]); private readonly T[] _array; private readonly int _offset; private readonly int _count; public ArraySegment(T[] array) { if (array == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); Contract.EndContractBlock(); _array = array; _offset = 0; _count = array.Length; } public ArraySegment(T[] array, int offset, int count) { // Validate arguments, check is minimal instructions with reduced branching for inlinable fast-path // Negative values discovered though conversion to high values when converted to unsigned // Failure should be rare and location determination and message is delegated to failure functions if (array == null || (uint)offset > (uint)array.Length || (uint)count > (uint)(array.Length - offset)) ThrowHelper.ThrowArraySegmentCtorValidationFailedExceptions(array, offset, count); Contract.EndContractBlock(); _array = array; _offset = offset; _count = count; } public T[] Array => _array; public int Offset => _offset; public int Count => _count; public T this[int index] { get { if ((uint)index >= (uint)_count) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); } return _array[_offset + index]; } set { if ((uint)index >= (uint)_count) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); } _array[_offset + index] = value; } } public Enumerator GetEnumerator() { ThrowInvalidOperationIfDefault(); return new Enumerator(this); } public override int GetHashCode() { if (_array == null) { return 0; } int hash = 5381; hash = System.Numerics.Hashing.HashHelpers.Combine(hash, _offset); hash = System.Numerics.Hashing.HashHelpers.Combine(hash, _count); // The array hash is expected to be an evenly-distributed mixture of bits, // so rather than adding the cost of another rotation we just xor it. hash ^= _array.GetHashCode(); return hash; } public void CopyTo(T[] destination) => CopyTo(destination, 0); public void CopyTo(T[] destination, int destinationIndex) { ThrowInvalidOperationIfDefault(); System.Array.Copy(_array, _offset, destination, destinationIndex, _count); } public void CopyTo(ArraySegment destination) { ThrowInvalidOperationIfDefault(); destination.ThrowInvalidOperationIfDefault(); if (_count > destination._count) { ThrowHelper.ThrowArgumentException_DestinationTooShort(); } System.Array.Copy(_array, _offset, destination._array, destination._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 ArraySegment Slice(int index) { ThrowInvalidOperationIfDefault(); if ((uint)index > (uint)_count) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); } return new ArraySegment(_array, _offset + index, _count - index); } public ArraySegment Slice(int index, int count) { ThrowInvalidOperationIfDefault(); if ((uint)index > (uint)_count || (uint)count > (uint)(_count - index)) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); } return new ArraySegment(_array, _offset + index, count); } public T[] ToArray() { ThrowInvalidOperationIfDefault(); if (_count == 0) { return Empty._array; } var array = new T[_count]; System.Array.Copy(_array, _offset, array, 0, _count); return array; } public static bool operator ==(ArraySegment a, ArraySegment b) { return a.Equals(b); } public static bool operator !=(ArraySegment a, ArraySegment b) { return !(a == b); } public static implicit operator ArraySegment(T[] array) => new ArraySegment(array); #region IList T IList.this[int index] { get { ThrowInvalidOperationIfDefault(); if (index < 0 || index >= _count) ThrowHelper.ThrowArgumentOutOfRange_IndexException(); Contract.EndContractBlock(); return _array[_offset + index]; } set { ThrowInvalidOperationIfDefault(); if (index < 0 || index >= _count) ThrowHelper.ThrowArgumentOutOfRange_IndexException(); Contract.EndContractBlock(); _array[_offset + index] = value; } } int IList.IndexOf(T item) { ThrowInvalidOperationIfDefault(); int index = System.Array.IndexOf(_array, item, _offset, _count); Debug.Assert(index == -1 || (index >= _offset && index < _offset + _count)); return index >= 0 ? index - _offset : -1; } void IList.Insert(int index, T item) { ThrowHelper.ThrowNotSupportedException(); } void IList.RemoveAt(int index) { ThrowHelper.ThrowNotSupportedException(); } #endregion #region IReadOnlyList T IReadOnlyList.this[int index] { get { ThrowInvalidOperationIfDefault(); if (index < 0 || index >= _count) ThrowHelper.ThrowArgumentOutOfRange_IndexException(); 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) { ThrowHelper.ThrowNotSupportedException(); } void ICollection.Clear() { ThrowHelper.ThrowNotSupportedException(); } bool ICollection.Contains(T item) { ThrowInvalidOperationIfDefault(); int index = System.Array.IndexOf(_array, item, _offset, _count); Debug.Assert(index == -1 || (index >= _offset && index < _offset + _count)); return index >= 0; } bool ICollection.Remove(T item) { ThrowHelper.ThrowNotSupportedException(); return default(bool); } #endregion #region IEnumerable IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion #region IEnumerable IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion private void ThrowInvalidOperationIfDefault() { if (_array == null) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NullArray); } } [Serializable] public struct Enumerator : IEnumerator { private readonly T[] _array; private readonly int _start; private readonly int _end; // cache Offset + Count, since it's a little slow private int _current; internal Enumerator(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 = arraySegment.Offset + arraySegment.Count; _current = arraySegment.Offset - 1; } public bool MoveNext() { if (_current < _end) { _current++; return (_current < _end); } return false; } public T Current { get { if (_current < _start) ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumNotStarted(); if (_current >= _end) ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumEnded(); return _array[_current]; } } object IEnumerator.Current => Current; void IEnumerator.Reset() { _current = _start - 1; } public void Dispose() { } } } }