File: System\Runtime\CompilerServices\ReadOnlyCollectionBuilder.cs
Web Access
Project: src\src\libraries\System.Linq.Expressions\src\System.Linq.Expressions.csproj (System.Linq.Expressions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
 
namespace System.Runtime.CompilerServices
{
    /// <summary>
    /// Builder for read only collections.
    /// </summary>
    /// <typeparam name="T">The type of the collection element.</typeparam>
    public sealed class ReadOnlyCollectionBuilder<T> : IList<T>, IList
    {
        private const int DefaultCapacity = 4;
 
        private T[] _items;
        private int _size;
        private int _version;
 
        /// <summary>
        /// Constructs a <see cref="ReadOnlyCollectionBuilder{T}"/>.
        /// </summary>
        public ReadOnlyCollectionBuilder()
        {
            _items = Array.Empty<T>();
        }
 
        /// <summary>
        /// Constructs a <see cref="ReadOnlyCollectionBuilder{T}"/> with a given initial capacity.
        /// The contents are empty but builder will have reserved room for the given
        /// number of elements before any reallocations are required.
        /// </summary>
        /// <param name="capacity">Initial capacity of the builder.</param>
        public ReadOnlyCollectionBuilder(int capacity)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(capacity);
 
            _items = new T[capacity];
        }
 
        /// <summary>
        /// Constructs a <see cref="ReadOnlyCollectionBuilder{T}"/>, copying contents of the given collection.
        /// </summary>
        /// <param name="collection">The collection whose elements to copy to the builder.</param>
        public ReadOnlyCollectionBuilder(IEnumerable<T> collection)
        {
            ArgumentNullException.ThrowIfNull(collection);
 
            if (collection is ICollection<T> c)
            {
                int count = c.Count;
                _items = new T[count];
                c.CopyTo(_items, 0);
                _size = count;
            }
            else
            {
                _size = 0;
                _items = new T[DefaultCapacity];
 
                using (IEnumerator<T> en = collection.GetEnumerator())
                {
                    while (en.MoveNext())
                    {
                        Add(en.Current);
                    }
                }
            }
        }
 
        /// <summary>
        /// Gets and sets the capacity of this <see cref="ReadOnlyCollectionBuilder{T}"/>.
        /// </summary>
        public int Capacity
        {
            get { return _items.Length; }
            set
            {
                ArgumentOutOfRangeException.ThrowIfLessThan(value, _size);
 
                if (value != _items.Length)
                {
                    if (value > 0)
                    {
                        T[] newItems = new T[value];
                        if (_size > 0)
                        {
                            Array.Copy(_items, newItems, _size);
                        }
                        _items = newItems;
                    }
                    else
                    {
                        _items = Array.Empty<T>();
                    }
                }
            }
        }
 
        /// <summary>
        /// Returns number of elements in the <see cref="ReadOnlyCollectionBuilder{T}"/>.
        /// </summary>
        public int Count => _size;
 
        #region IList<T> Members
 
        /// <summary>
        /// Returns the index of the first occurrence of a given value in the builder.
        /// </summary>
        /// <param name="item">An item to search for.</param>
        /// <returns>The index of the first occurrence of an item.</returns>
        public int IndexOf(T item)
        {
            return Array.IndexOf(_items, item, 0, _size);
        }
 
        /// <summary>
        /// Inserts an item to the <see cref="ReadOnlyCollectionBuilder{T}"/> at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which item should be inserted.</param>
        /// <param name="item">The object to insert into the <see cref="ReadOnlyCollectionBuilder{T}"/>.</param>
        public void Insert(int index, T item)
        {
            ArgumentOutOfRangeException.ThrowIfGreaterThan(index, _size);
 
            if (_size == _items.Length)
            {
                EnsureCapacity(_size + 1);
            }
            if (index < _size)
            {
                Array.Copy(_items, index, _items, index + 1, _size - index);
            }
            _items[index] = item;
            _size++;
            _version++;
        }
 
        /// <summary>
        /// Removes the <see cref="ReadOnlyCollectionBuilder{T}"/> item at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the item to remove.</param>
        public void RemoveAt(int index)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(index);
            ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _size);
 
            _size--;
            if (index < _size)
            {
                Array.Copy(_items, index + 1, _items, index, _size - index);
            }
            _items[_size] = default(T)!;
            _version++;
        }
 
        /// <summary>
        ///  Gets or sets the element at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the element to get or set.</param>
        /// <returns>The element at the specified index.</returns>
        public T this[int index]
        {
            get
            {
                ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _size);
 
                return _items[index];
            }
            set
            {
                ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _size);
 
                _items[index] = value;
                _version++;
            }
        }
 
        #endregion
 
        #region ICollection<T> Members
 
        /// <summary>
        /// Adds an item to the <see cref="ReadOnlyCollectionBuilder{T}"/>.
        /// </summary>
        /// <param name="item">The object to add to the <see cref="ReadOnlyCollectionBuilder{T}"/>.</param>
        public void Add(T item)
        {
            if (_size == _items.Length)
            {
                EnsureCapacity(_size + 1);
            }
            _items[_size++] = item;
            _version++;
        }
 
        /// <summary>
        /// Removes all items from the <see cref="ReadOnlyCollectionBuilder{T}"/>.
        /// </summary>
        public void Clear()
        {
            if (_size > 0)
            {
                Array.Clear(_items, 0, _size);
                _size = 0;
            }
            _version++;
        }
 
        /// <summary>
        /// Determines whether the <see cref="ReadOnlyCollectionBuilder{T}"/> contains a specific value
        /// </summary>
        /// <param name="item">the object to locate in the <see cref="ReadOnlyCollectionBuilder{T}"/>.</param>
        /// <returns>true if item is found in the <see cref="ReadOnlyCollectionBuilder{T}"/>; otherwise, false.</returns>
        public bool Contains(T item) => IndexOf(item) >= 0;
 
        /// <summary>
        /// Copies the elements of the <see cref="ReadOnlyCollectionBuilder{T}"/> to an <see cref="Array"/>,
        /// starting at particular <see cref="Array"/> index.
        /// </summary>
        /// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="ReadOnlyCollectionBuilder{T}"/>.</param>
        /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
        public void CopyTo(T[] array, int arrayIndex) => Array.Copy(_items, 0, array, arrayIndex, _size);
 
        bool ICollection<T>.IsReadOnly => false;
 
        /// <summary>
        /// Removes the first occurrence of a specific object from the <see cref="ReadOnlyCollectionBuilder{T}"/>.
        /// </summary>
        /// <param name="item">The object to remove from the <see cref="ReadOnlyCollectionBuilder{T}"/>.</param>
        /// <returns>true if item was successfully removed from the <see cref="ReadOnlyCollectionBuilder{T}"/>;
        /// otherwise, false. This method also returns false if item is not found in the original <see cref="ReadOnlyCollectionBuilder{T}"/>.
        /// </returns>
        public bool Remove(T item)
        {
            int index = IndexOf(item);
            if (index >= 0)
            {
                RemoveAt(index);
                return true;
            }
 
            return false;
        }
 
        #endregion
 
        #region IEnumerable<T> Members
 
        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>A <see cref="IEnumerator{T}"/> that can be used to iterate through the collection.</returns>
        public IEnumerator<T> GetEnumerator() => new Enumerator(this);
 
        #endregion
 
        #region IEnumerable Members
 
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 
        #endregion
 
        #region IList Members
 
        bool IList.IsReadOnly => false;
 
        int IList.Add(object? value)
        {
            ValidateNullValue(value, nameof(value));
            try
            {
                Add((T)value!);
            }
            catch (InvalidCastException)
            {
                throw Error.InvalidTypeException(value, typeof(T), nameof(value));
            }
            return Count - 1;
        }
 
        bool IList.Contains(object? value)
        {
            if (IsCompatibleObject(value))
            {
                return Contains((T)value!);
            }
            else return false;
        }
 
        int IList.IndexOf(object? value)
        {
            if (IsCompatibleObject(value))
            {
                return IndexOf((T)value!);
            }
            return -1;
        }
 
        void IList.Insert(int index, object? value)
        {
            ValidateNullValue(value, nameof(value));
            try
            {
                Insert(index, (T)value!);
            }
            catch (InvalidCastException)
            {
                throw Error.InvalidTypeException(value, typeof(T), nameof(value));
            }
        }
 
        bool IList.IsFixedSize => false;
 
        void IList.Remove(object? value)
        {
            if (IsCompatibleObject(value))
            {
                Remove((T)value!);
            }
        }
 
        object? IList.this[int index]
        {
            get
            {
                return this[index];
            }
            set
            {
                ValidateNullValue(value, nameof(value));
 
                try
                {
                    this[index] = (T)value!;
                }
                catch (InvalidCastException)
                {
                    throw Error.InvalidTypeException(value, typeof(T), nameof(value));
                }
            }
        }
 
        #endregion
 
        #region ICollection Members
 
        void ICollection.CopyTo(Array array, int index)
        {
            ArgumentNullException.ThrowIfNull(array);
 
            if (array.Rank != 1)
                throw new ArgumentException(nameof(array));
 
            Array.Copy(_items, 0, array, index, _size);
        }
 
        bool ICollection.IsSynchronized => false;
 
        object ICollection.SyncRoot => this;
 
        #endregion
 
        /// <summary>
        /// Reverses the order of the elements in the entire <see cref="ReadOnlyCollectionBuilder{T}"/>.
        /// </summary>
        public void Reverse()
        {
            Reverse(0, Count);
        }
 
        /// <summary>
        /// Reverses the order of the elements in the specified range.
        /// </summary>
        /// <param name="index">The zero-based starting index of the range to reverse.</param>
        /// <param name="count">The number of elements in the range to reverse.</param>
        public void Reverse(int index, int count)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(index);
            ArgumentOutOfRangeException.ThrowIfNegative(count);
 
            Array.Reverse(_items, index, count);
            _version++;
        }
 
        /// <summary>
        /// Copies the elements of the <see cref="ReadOnlyCollectionBuilder{T}"/> to a new array.
        /// </summary>
        /// <returns>An array containing copies of the elements of the <see cref="ReadOnlyCollectionBuilder{T}"/>.</returns>
        public T[] ToArray()
        {
            T[] array = new T[_size];
            Array.Copy(_items, array, _size);
            return array;
        }
 
        /// <summary>
        /// Creates a <see cref="ReadOnlyCollection{T}"/> containing all of the elements of the <see cref="ReadOnlyCollectionBuilder{T}"/>,
        /// avoiding copying the elements to the new array if possible. Resets the <see cref="ReadOnlyCollectionBuilder{T}"/> after the
        /// <see cref="ReadOnlyCollection{T}"/> has been created.
        /// </summary>
        /// <returns>A new instance of <see cref="ReadOnlyCollection{T}"/>.</returns>
        public ReadOnlyCollection<T> ToReadOnlyCollection()
        {
            if (_size == 0)
            {
                return ReadOnlyCollection<T>.Empty;
            }
 
            // Can we use the stored array?
            T[] items;
            if (_size == _items.Length)
            {
                items = _items;
            }
            else
            {
                items = ToArray();
            }
            _items = Array.Empty<T>();
            _size = 0;
            _version++;
 
            return new TrueReadOnlyCollection<T>(items);
        }
 
        private void EnsureCapacity(int min)
        {
            if (_items.Length < min)
            {
                int newCapacity = DefaultCapacity;
                if (_items.Length > 0)
                {
                    newCapacity = _items.Length * 2;
                }
                if (newCapacity < min)
                {
                    newCapacity = min;
                }
                Capacity = newCapacity;
            }
        }
 
        private static bool IsCompatibleObject(object? value)
        {
            return ((value is T) || (value == null && default(T) == null));
        }
 
        private static void ValidateNullValue(object? value, string argument)
        {
            if (value == null && default(T) != null)
            {
                throw Error.InvalidNullValue(typeof(T), argument);
            }
        }
 
        private sealed class Enumerator : IEnumerator<T>, IEnumerator
        {
            private readonly ReadOnlyCollectionBuilder<T> _builder;
            private readonly int _version;
 
            private int _index;
            private T _current;
 
            internal Enumerator(ReadOnlyCollectionBuilder<T> builder)
            {
                _builder = builder;
                _version = builder._version;
                _index = 0;
                _current = default(T)!;
            }
 
            #region IEnumerator<T> Members
 
            public T Current => _current;
 
            #endregion
 
            #region IDisposable Members
 
            public void Dispose()
            {
            }
 
            #endregion
 
            #region IEnumerator Members
 
            object? IEnumerator.Current
            {
                get
                {
                    if (_index == 0 || _index > _builder._size)
                    {
                        throw Error.EnumerationIsDone();
                    }
                    return _current;
                }
            }
 
            public bool MoveNext()
            {
                if (_version == _builder._version)
                {
                    if (_index < _builder._size)
                    {
                        _current = _builder._items[_index++];
                        return true;
                    }
                    else
                    {
                        _index = _builder._size + 1;
                        _current = default(T)!;
                        return false;
                    }
                }
                else
                {
                    throw Error.CollectionModifiedWhileEnumerating();
                }
            }
 
            #endregion
 
            #region IEnumerator Members
 
            void IEnumerator.Reset()
            {
                if (_version != _builder._version)
                {
                    throw Error.CollectionModifiedWhileEnumerating();
                }
                _index = 0;
                _current = default(T)!;
            }
 
            #endregion
        }
    }
}