| File: InternalUtilities\WeakList.cs | Web Access |
| Project: src\roslyn\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; } } } }