File: System\Net\Http\Headers\ObjectCollection.cs
Web Access
Project: src\src\libraries\System.Net.Http\src\System.Net.Http.csproj (System.Net.Http)
// 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.Diagnostics;
 
namespace System.Net.Http.Headers
{
    internal sealed class UnvalidatedObjectCollection<T> : ObjectCollection<T> where T : class
    {
        public override void Validate(T item)
        {
            ArgumentNullException.ThrowIfNull(item);
        }
    }
 
    /// <summary>An <see cref="ICollection{T}"/> list that prohibits null elements and that is optimized for a small number of elements.</summary>
    [DebuggerDisplay("Count = {Count}")]
    [DebuggerTypeProxy(typeof(ObjectCollection<>.DebugView))]
    internal abstract class ObjectCollection<T> : ICollection<T> where T : class
    {
        private const int DefaultSize = 4;
 
        /// <summary>null, a T, or a T[].</summary>
        internal object? _items;
        /// <summary>Number of elements stored in the collection.</summary>
        internal int _size;
 
        public ObjectCollection() { }
 
        public int Count => _size;
 
        public bool IsReadOnly => false;
 
        public abstract void Validate(T item);
 
        public void Add(T item)
        {
            Validate(item);
            Debug.Assert(item != null);
 
            if (_items is null)
            {
                // The collection is empty. Just store the new item directly.
                _items = item;
                _size = 1;
            }
            else if (_items is T existingItem)
            {
                // The collection has a single item stored directly.  Upgrade to
                // an array, and store both the existing and new items.
                Debug.Assert(_size == 1);
                T[] items = new T[DefaultSize];
                items[0] = existingItem;
                items[1] = item;
                _items = items;
                _size = 2;
            }
            else
            {
                T[] array = (T[])_items;
                int size = _size;
                if ((uint)size < (uint)array.Length)
                {
                    // There's room in the existing array.  Add the item.
                    array[size] = item;
                }
                else
                {
                    // We need to grow the array.  Do so, and store the new item.
                    Debug.Assert(_size > 0);
                    Debug.Assert(_size == array.Length);
 
                    var newItems = new T[array.Length * 2];
                    Array.Copy(array, newItems, size);
                    _items = newItems;
                    newItems[size] = item;
                }
                _size = size + 1;
            }
        }
 
        public void Clear()
        {
            _items = null;
            _size = 0;
        }
 
        public bool Contains(T item) =>
            _size <= 0 ? false :
            _items is T o ? o.Equals(item) :
            _items is T[] items && Array.IndexOf(items, item, 0, _size) != -1;
 
        public void CopyTo(T[] array, int arrayIndex)
        {
            if (_items is T[] items)
            {
                Array.Copy(items, 0, array, arrayIndex, _size);
            }
            else
            {
                Debug.Assert(_size == 0 || _size == 1);
                if (array is null || _size > array.Length - arrayIndex)
                {
                    // Use Array.CopyTo to throw the right exceptions.
                    new T[] { (T)_items! }.CopyTo(array!, arrayIndex);
                }
                else if (_size == 1)
                {
                    array[arrayIndex] = (T)_items!;
                }
            }
        }
 
        public bool Remove(T item)
        {
            if (_items is T o)
            {
                if (o.Equals(item))
                {
                    _items = null;
                    _size = 0;
                    return true;
                }
            }
            else if (_items is T[] items)
            {
                int index = Array.IndexOf(items, item, 0, _size);
                if (index != -1)
                {
                    _size--;
                    if (index < _size)
                    {
                        Array.Copy(items, index + 1, items, index, _size - index);
                    }
                    items[_size] = null!;
 
                    return true;
                }
            }
 
            return false;
        }
 
        public Enumerator GetEnumerator() => new Enumerator(this);
        IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 
        public struct Enumerator : IEnumerator<T>
        {
            private readonly ObjectCollection<T> _list;
            private int _index;
            private T _current;
 
            internal Enumerator(ObjectCollection<T> list)
            {
                _list = list;
                _index = 0;
                _current = default!;
            }
 
            public void Dispose() { }
 
            public bool MoveNext()
            {
                ObjectCollection<T> list = _list;
 
                if ((uint)_index < (uint)list._size)
                {
                    _current = list._items is T[] items ? items[_index] : (T)list._items!;
                    _index++;
                    return true;
                }
 
                _index = _list._size + 1;
                _current = default!;
                return false;
            }
 
            public T Current => _current!;
 
            object? IEnumerator.Current => _current;
 
            void IEnumerator.Reset()
            {
                _index = 0;
                _current = default!;
            }
        }
 
        internal sealed class DebugView
        {
            private readonly ObjectCollection<T> _collection;
 
            public DebugView(ObjectCollection<T> collection)
            {
                ArgumentNullException.ThrowIfNull(collection);
                _collection = collection;
            }
 
            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
            public T[] Items
            {
                get
                {
                    T[] items = new T[_collection.Count];
                    _collection.CopyTo(items, 0);
                    return items;
                }
            }
        }
    }
}