File: InternalUtilities\WeakList.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
 
namespace Roslyn.Utilities
{
    /// <summary>
    /// Represents an ordered sequence of weak references.
    /// </summary>
    internal sealed class WeakList<T>
        where T : class
    {
        private WeakReference<T>[] _items;
        private int _size;
 
        public WeakList()
        {
            _items = Array.Empty<WeakReference<T>>();
        }
 
        private void Resize()
        {
            Debug.Assert(_size == _items.Length);
            Debug.Assert(_items.Length == 0 || _items.Length >= MinimalNonEmptySize);
 
            int alive = _items.Length;
            int firstDead = -1;
            for (int i = 0; i < _items.Length; i++)
            {
                if (!_items[i].TryGetTarget(out _))
                {
                    if (firstDead == -1)
                    {
                        firstDead = i;
                    }
 
                    alive--;
                }
            }
 
            if (alive < _items.Length / 4)
            {
                // If we have just a few items left we shrink the array.
                // We avoid expanding the array until the number of new items added exceeds half of its capacity.
                Shrink(firstDead, alive);
            }
            else if (alive >= 3 * _items.Length / 4)
            {
                // If we have a lot of items alive we expand the array since just compacting them 
                // wouldn't free up much space (we would end up calling Resize again after adding a few more items).
                var newItems = new WeakReference<T>[GetExpandedSize(_items.Length)];
 
                if (firstDead >= 0)
                {
                    Compact(firstDead, newItems);
                }
                else
                {
                    Array.Copy(_items, 0, newItems, 0, _items.Length);
                    Debug.Assert(_size == _items.Length);
                }
 
                _items = newItems;
            }
            else
            {
                // Compact in-place to make space for new items at the end.
                // We will free up to length/4 slots in the array.
                Compact(firstDead, _items);
            }
 
            Debug.Assert(_items.Length > 0 && _size < 3 * _items.Length / 4, "length: " + _items.Length + " size: " + _size);
        }
 
        private void Shrink(int firstDead, int alive)
        {
            int newSize = GetExpandedSize(alive);
            var newItems = (newSize == _items.Length) ? _items : new WeakReference<T>[newSize];
            Compact(firstDead, newItems);
            _items = newItems;
        }
 
        private const int MinimalNonEmptySize = 4;
 
        private static int GetExpandedSize(int baseSize)
        {
            return Math.Max((baseSize * 2) + 1, MinimalNonEmptySize);
        }
 
        /// <summary>
        /// Copies all live references from <see cref="_items"/> to <paramref name="result"/>.
        /// Assumes that all references prior <paramref name="firstDead"/> are alive.
        /// </summary>
        private void Compact(int firstDead, WeakReference<T>[] result)
        {
            Debug.Assert(_items[firstDead].IsNull());
 
            if (!ReferenceEquals(_items, result))
            {
                Array.Copy(_items, 0, result, 0, firstDead);
            }
 
            int oldSize = _size;
            int j = firstDead;
            for (int i = firstDead + 1; i < oldSize; i++)
            {
                var item = _items[i];
 
                if (item.TryGetTarget(out _))
                {
                    result[j++] = item;
                }
            }
 
            _size = j;
 
            // free WeakReferences
            if (ReferenceEquals(_items, result))
            {
                while (j < oldSize)
                {
                    _items[j++] = null!;
                }
            }
        }
 
        /// <summary>
        /// Returns the number of weak references in this list. 
        /// Note that some of them might not point to live objects anymore.
        /// </summary>
        public int WeakCount
        {
            get { return _size; }
        }
 
        public WeakReference<T> GetWeakReference(int index)
        {
            if (index < 0 || index >= _size)
            {
                throw new ArgumentOutOfRangeException(nameof(index));
            }
 
            return _items[index];
        }
 
        public void Add(T item)
        {
            if (_size == _items.Length)
            {
                Resize();
            }
 
            Debug.Assert(_size < _items.Length);
            _items[_size++] = new WeakReference<T>(item);
        }
 
        public struct Enumerator
        {
            private readonly WeakList<T> _weakList;
            private readonly int _count;
            private int _nextIndex;
            private int _alive;
            private int _firstDead;
            private T? _current;
 
            public Enumerator(WeakList<T> weakList)
            {
                _weakList = weakList;
                _nextIndex = 0;
                _count = weakList._size;
                _alive = weakList._size;
                _firstDead = -1;
                _current = null;
            }
 
            public T Current => _current!;
 
            public bool MoveNext()
            {
                while (_nextIndex < _count)
                {
                    int currentIndex = _nextIndex;
                    _nextIndex += 1;
                    if (_weakList._items[currentIndex].TryGetTarget(out var item))
                    {
                        _current = item;
                        return true;
                    }
                    else
                    {
                        // object has been collected 
 
                        if (_firstDead < 0)
                        {
                            _firstDead = currentIndex;
                        }
 
                        _alive--;
                    }
                }
 
                if (_alive == 0)
                {
                    _weakList._items = Array.Empty<WeakReference<T>>();
                    _weakList._size = 0;
                }
                else if (_alive < _weakList._items.Length / 4)
                {
                    // If we have just a few items left we shrink the array.
                    _weakList.Shrink(_firstDead, _alive);
                }
 
                return false;
            }
        }
 
        public Enumerator GetEnumerator()
        {
            return new Enumerator(this);
        }
 
        internal WeakReference<T>[] TestOnly_UnderlyingArray { get { return _items; } }
    }
}