File: system\arraysegment.cs
Project: ndp\clr\src\bcl\mscorlib.csproj (mscorlib)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
/*============================================================
**
** Class:  ArraySegment<T>
**
**
** 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<T> : IList<T>, IReadOnlyList<T>
    {
        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<int>() >= 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<int>() >= 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<T>)
                return Equals((ArraySegment<T>)obj);
            else
                return false;
        }
    
        public bool Equals(ArraySegment<T> obj)
        {
            return obj._array == _array && obj._offset == _offset && obj._count == _count;
        }
    
        public static bool operator ==(ArraySegment<T> a, ArraySegment<T> b)
        {
            return a.Equals(b);
        }
        
        public static bool operator !=(ArraySegment<T> a, ArraySegment<T> b)
        {
            return !(a == b);
        }
 
        #region IList<T>
        T IList<T>.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<T>.IndexOf(T item)
        {
            if (_array == null)
                throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullArray"));
            Contract.EndContractBlock();
 
            int index = System.Array.IndexOf<T>(_array, item, _offset, _count);
 
            Contract.Assert(index == -1 || 
                            (index >= _offset && index < _offset + _count));
 
            return index >= 0 ? index - _offset : -1;
        }
 
        void IList<T>.Insert(int index, T item)
        {
            throw new NotSupportedException();
        }
 
        void IList<T>.RemoveAt(int index)
        {
            throw new NotSupportedException();
        }
        #endregion
 
        #region IReadOnlyList<T>
        T IReadOnlyList<T>.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<T>
 
        #region ICollection<T>
        bool ICollection<T>.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<T>.Add(T item)
        {
            throw new NotSupportedException();
        }
 
        void ICollection<T>.Clear()
        {
            throw new NotSupportedException();
        }
 
        bool ICollection<T>.Contains(T item)
        {
            if (_array == null)
                throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullArray"));
            Contract.EndContractBlock();
 
            int index = System.Array.IndexOf<T>(_array, item, _offset, _count);
 
            Contract.Assert(index == -1 ||
                            (index >= _offset && index < _offset + _count));
 
            return index >= 0;
        }
 
        void ICollection<T>.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<T>.Remove(T item)
        {
            throw new NotSupportedException();
        }
        #endregion
 
        #region IEnumerable<T>
        IEnumerator<T> IEnumerable<T>.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<T>
        {
            private T[] _array;
            private int _start;
            private int _end;
            private int _current;
 
            internal ArraySegmentEnumerator(ArraySegment<T> 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()
            {
            }
        }
    }
}