#region CPL License /* Nuclex Framework Copyright (C) 2002-2017 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections; using System.Collections.Generic; namespace Nuclex.Support.Collections { /// Collection of weakly referenced objects /// /// This collection tries to expose the interface of a normal collection, but stores /// objects as weak references. When an object is accessed, it can return null. /// when the collection detects that one of its items was garbage collected, it /// will silently remove that item. /// public partial class WeakCollection : IList, IList where TItem : class { #region class UnpackingEnumerator /// /// An enumerator that unpacks the items returned by an enumerator of the /// weak reference collection into the actual item type on-the-fly. /// private class UnpackingEnumerator : IEnumerator { /// Initializes a new unpacking enumerator /// /// Enumerator of the weak reference collection /// public UnpackingEnumerator( IEnumerator> containedTypeEnumerator ) { this.containedTypeEnumerator = containedTypeEnumerator; } /// Immediately releases all resources used by the instance public void Dispose() { this.containedTypeEnumerator.Dispose(); } /// /// The element in the collection at the current position of the enumerator. /// public TItem Current { get { return this.containedTypeEnumerator.Current.Target; } } /// Gets the current element in the collection. /// The current element in the collection. /// /// The enumerator is positioned before the first element of the collection /// or after the last element. /// public bool MoveNext() { return this.containedTypeEnumerator.MoveNext(); } /// /// Sets the enumerator to its initial position, which is before the first element /// in the collection. /// /// /// The collection was modified after the enumerator was created. /// public void Reset() { this.containedTypeEnumerator.Reset(); } /// The current element in the collection. /// /// The enumerator is positioned before the first element of the collection /// or after the last element. /// object IEnumerator.Current { get { return Current; } } /// An enumerator from the wrapped collection private IEnumerator> containedTypeEnumerator; } #endregion // class UnpackingEnumerator /// Initializes a new weak reference collection /// /// Internal list of weak references that are unpacking when accessed through /// the WeakCollection's interface. /// public WeakCollection(IList> items) : this(items, EqualityComparer.Default) { } /// Initializes a new weak reference collection /// /// Internal list of weak references that are unpacking when accessed through /// the WeakCollection's interface. /// /// /// Comparer used to identify and compare items to each other /// public WeakCollection( IList> items, IEqualityComparer comparer ) { this.items = items; this.comparer = comparer; } /// /// Determines whether an element is in the WeakCollection /// /// /// The object to locate in the WeakCollection. The value can be null. /// /// /// True if value is found in the WeakCollection; otherwise, false. /// /// /// The default implementation of this method is very unoptimized and will /// enumerate all the items in the collection, transforming one after another /// to check whether the transformed item matches the item the user was /// looking for. It is recommended to provide a custom implementation of /// this method, if possible. /// public virtual bool Contains(TItem item) { return (IndexOf(item) != -1); } /// /// Copies the entire WeakCollection to a compatible one-dimensional /// System.Array, starting at the specified index of the target array. /// /// /// The one-dimensional System.Array that is the destination of the elements copied /// from the WeakCollection. The System.Array must have zero-based indexing. /// /// /// The zero-based index in array at which copying begins. /// /// /// Index is equal to or greater than the length of array or the number of elements /// in the source WeakCollection is greater than the available space from index to /// the end of the destination array. /// /// /// Index is less than zero. /// /// /// Array is null. /// public void CopyTo(TItem[] array, int index) { if(this.items.Count > (array.Length - index)) { throw new ArgumentException( "Array too small to fit the collection items starting at the specified index" ); } for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) { array[itemIndex + index] = this.items[itemIndex].Target; } } /// Removes all items from the WeakCollection public void Clear() { this.items.Clear(); } /// /// Returns an enumerator that iterates through the WeakCollection. /// /// An enumerator or the WeakCollection. public IEnumerator GetEnumerator() { return new UnpackingEnumerator(this.items.GetEnumerator()); } /// /// Searches for the specified object and returns the zero-based index of the /// first occurrence within the entire WeakCollection. /// /// /// The object to locate in the WeakCollection. The value can /// be null for reference types. /// /// /// The zero-based index of the first occurrence of item within the entire /// WeakCollection, if found; otherwise, -1. /// /// /// The default implementation of this method is very unoptimized and will /// enumerate all the items in the collection, transforming one after another /// to check whether the transformed item matches the item the user was /// looking for. It is recommended to provide a custom implementation of /// this method, if possible. /// public int IndexOf(TItem item) { for(int index = 0; index < this.items.Count; ++index) { TItem itemAtIndex = this.items[index].Target; if((itemAtIndex == null) || (item == null)) { if(ReferenceEquals(item, itemAtIndex)) { return index; } } else { if(this.comparer.Equals(itemAtIndex, item)) { return index; } } } return -1; } /// /// The number of elements contained in the WeakCollection instance /// public int Count { get { return this.items.Count; } } /// Gets the element at the specified index. /// The zero-based index of the element to get. /// The element at the specified index. /// /// Index is less than zero or index is equal to or greater than /// WeakCollection.Count. /// public TItem this[int index] { get { return this.items[index].Target; } set { this.items[index] = new WeakReference(value); } } /// /// Removes the first occurrence of a specific object from the WeakCollection. /// /// The object to remove from the WeakCollection /// /// True if item was successfully removed from the WeakCollection; otherwise, false. /// public bool Remove(TItem item) { for(int index = 0; index < this.items.Count; ++index) { TItem itemAtIndex = this.items[index].Target; if((itemAtIndex == null) || (item == null)) { if(ReferenceEquals(item, itemAtIndex)) { this.items.RemoveAt(index); return true; } } else { if(this.comparer.Equals(item, itemAtIndex)) { this.items.RemoveAt(index); return true; } } } return false; } /// Adds an item to the WeakCollection. /// The object to add to the WeakCollection public void Add(TItem item) { this.items.Add(new WeakReference(item)); } /// Inserts an item to the WeakCollection at the specified index. /// /// The zero-based index at which item should be inserted. /// /// The object to insert into the WeakCollection /// /// index is not a valid index in the WeakCollection. /// public void Insert(int index, TItem item) { this.items.Insert(index, new WeakReference(item)); } /// /// Removes the WeakCollection item at the specified index. /// /// The zero-based index of the item to remove. /// /// Index is not a valid index in the WeakCollection. /// public void RemoveAt(int index) { this.items.RemoveAt(index); } /// Whether the List is write-protected public bool IsReadOnly { get { return this.items.IsReadOnly; } } /// /// Removes the items that have been garbage collected from the collection /// public void RemoveDeadItems() { int newCount = 0; // Eliminate all items that have been garbage collected by shifting for(int index = 0; index < this.items.Count; ++index) { if(this.items[index].IsAlive) { this.items[newCount] = this.items[index]; ++newCount; } } // If any garbage collected items were found, resize the collection so // the space that became empty in the previous shifting process will be freed while(this.items.Count > newCount) { this.items.RemoveAt(this.items.Count - 1); } } /// Weak references to the items contained in the collection private IList> items; /// Used to identify and compare items in the collection private IEqualityComparer comparer; /// Synchronization root for threaded accesses to this collection private object syncRoot; } } // namespace Nuclex.Support.Collections