Nuclex.Support/Source/Collections/WeakCollection.cs
2024-06-13 18:36:21 +02:00

339 lines
12 KiB
C#

#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
/// <summary>Collection of weakly referenced objects</summary>
/// <remarks>
/// 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.
/// </remarks>
public partial class WeakCollection<TItem> : IList<TItem>, IList
where TItem : class {
#region class UnpackingEnumerator
/// <summary>
/// An enumerator that unpacks the items returned by an enumerator of the
/// weak reference collection into the actual item type on-the-fly.
/// </summary>
private class UnpackingEnumerator : IEnumerator<TItem> {
/// <summary>Initializes a new unpacking enumerator</summary>
/// <param name="containedTypeEnumerator">
/// Enumerator of the weak reference collection
/// </param>
public UnpackingEnumerator(
IEnumerator<WeakReference<TItem>> containedTypeEnumerator
) {
this.containedTypeEnumerator = containedTypeEnumerator;
}
/// <summary>Immediately releases all resources used by the instance</summary>
public void Dispose() {
this.containedTypeEnumerator.Dispose();
}
/// <summary>
/// The element in the collection at the current position of the enumerator.
/// </summary>
public TItem Current {
get { return this.containedTypeEnumerator.Current.Target; }
}
/// <summary>Gets the current element in the collection.</summary>
/// <returns>The current element in the collection.</returns>
/// <exception cref="System.InvalidOperationException">
/// The enumerator is positioned before the first element of the collection
/// or after the last element.
/// </exception>
public bool MoveNext() {
return this.containedTypeEnumerator.MoveNext();
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element
/// in the collection.
/// </summary>
/// <exception cref="System.InvalidOperationException">
/// The collection was modified after the enumerator was created.
/// </exception>
public void Reset() {
this.containedTypeEnumerator.Reset();
}
/// <summary>The current element in the collection.</summary>
/// <exception cref="System.InvalidOperationException">
/// The enumerator is positioned before the first element of the collection
/// or after the last element.
/// </exception>
object IEnumerator.Current {
get { return Current; }
}
/// <summary>An enumerator from the wrapped collection</summary>
private IEnumerator<WeakReference<TItem>> containedTypeEnumerator;
}
#endregion // class UnpackingEnumerator
/// <summary>Initializes a new weak reference collection</summary>
/// <param name="items">
/// Internal list of weak references that are unpacking when accessed through
/// the WeakCollection's interface.
/// </param>
public WeakCollection(IList<WeakReference<TItem>> items) :
this(items, EqualityComparer<TItem>.Default) { }
/// <summary>Initializes a new weak reference collection</summary>
/// <param name="items">
/// Internal list of weak references that are unpacking when accessed through
/// the WeakCollection's interface.
/// </param>
/// <param name="comparer">
/// Comparer used to identify and compare items to each other
/// </param>
public WeakCollection(
IList<WeakReference<TItem>> items, IEqualityComparer<TItem> comparer
) {
this.items = items;
this.comparer = comparer;
}
/// <summary>
/// Determines whether an element is in the WeakCollection
/// </summary>
/// <param name="item">
/// The object to locate in the WeakCollection. The value can be null.
/// </param>
/// <returns>
/// True if value is found in the WeakCollection; otherwise, false.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
public virtual bool Contains(TItem item) {
return (IndexOf(item) != -1);
}
/// <summary>
/// Copies the entire WeakCollection to a compatible one-dimensional
/// System.Array, starting at the specified index of the target array.
/// </summary>
/// <param name="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.
/// </param>
/// <param name="index">
/// The zero-based index in array at which copying begins.
/// </param>
/// <exception cref="System.ArgumentException">
/// 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.
/// </exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Index is less than zero.
/// </exception>
/// <exception cref="System.ArgumentNullException">
/// Array is null.
/// </exception>
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;
}
}
/// <summary>Removes all items from the WeakCollection</summary>
public void Clear() {
this.items.Clear();
}
/// <summary>
/// Returns an enumerator that iterates through the WeakCollection.
/// </summary>
/// <returns>An enumerator or the WeakCollection.</returns>
public IEnumerator<TItem> GetEnumerator() {
return new UnpackingEnumerator(this.items.GetEnumerator());
}
/// <summary>
/// Searches for the specified object and returns the zero-based index of the
/// first occurrence within the entire WeakCollection.
/// </summary>
/// <param name="item">
/// The object to locate in the WeakCollection. The value can
/// be null for reference types.
/// </param>
/// <returns>
/// The zero-based index of the first occurrence of item within the entire
/// WeakCollection, if found; otherwise, -1.
/// </returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
/// <summary>
/// The number of elements contained in the WeakCollection instance
/// </summary>
public int Count {
get { return this.items.Count; }
}
/// <summary>Gets the element at the specified index.</summary>
/// <param name="index">The zero-based index of the element to get.</param>
/// <returns>The element at the specified index.</returns>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Index is less than zero or index is equal to or greater than
/// WeakCollection.Count.
/// </exception>
public TItem this[int index] {
get { return this.items[index].Target; }
set { this.items[index] = new WeakReference<TItem>(value); }
}
/// <summary>
/// Removes the first occurrence of a specific object from the WeakCollection.
/// </summary>
/// <param name="item">The object to remove from the WeakCollection</param>
/// <returns>
/// True if item was successfully removed from the WeakCollection; otherwise, false.
/// </returns>
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;
}
/// <summary>Adds an item to the WeakCollection.</summary>
/// <param name="item">The object to add to the WeakCollection</param>
public void Add(TItem item) {
this.items.Add(new WeakReference<TItem>(item));
}
/// <summary>Inserts an item to the WeakCollection at the specified index.</summary>
/// <param name="index">
/// The zero-based index at which item should be inserted.
/// </param>
/// <param name="item">The object to insert into the WeakCollection</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// index is not a valid index in the WeakCollection.
/// </exception>
public void Insert(int index, TItem item) {
this.items.Insert(index, new WeakReference<TItem>(item));
}
/// <summary>
/// Removes the WeakCollection item at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the item to remove.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Index is not a valid index in the WeakCollection.
/// </exception>
public void RemoveAt(int index) {
this.items.RemoveAt(index);
}
/// <summary>Whether the List is write-protected</summary>
public bool IsReadOnly {
get { return this.items.IsReadOnly; }
}
/// <summary>
/// Removes the items that have been garbage collected from the collection
/// </summary>
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);
}
}
/// <summary>Weak references to the items contained in the collection</summary>
private IList<WeakReference<TItem>> items;
/// <summary>Used to identify and compare items in the collection</summary>
private IEqualityComparer<TItem> comparer;
/// <summary>Synchronization root for threaded accesses to this collection</summary>
private object syncRoot;
}
} // namespace Nuclex.Support.Collections