#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;
using System.Collections.ObjectModel;
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