using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading; namespace Nuclex.Support.Collections { /// Collection that transforms the contents of another collection. /// /// Type of the items contained in the wrapped collection. /// /// /// Type this collection exposes its items as. /// /// /// /// This collection is useful if you want to expose the objects of an arbitrary /// collection under a different type. It can be used, for example, to construct /// wrappers for the items in a collection on-the-fly, eliminating the need to /// manage the wrappers in parallel to the real items and improving performance /// by only constructing a wrapper when an item is actually requested. /// /// /// Another common use would be if you have a private collection of a non-public /// type that's derived from some publicly visible type. By using this collection, /// you can return the items under the publicly visible type while still having /// your private collection under the non-public type, eliminating the need to /// downcast each time you need to access elements of the non-public type. /// /// public abstract class TransformingReadOnlyCollection : IList, IList { #region class TransformingEnumerator /// /// An enumerator that transforms the items returned by an enumerator of the /// wrapped collection into the exposed type on-the-fly. /// private class TransformingEnumerator : IEnumerator { /// Initializes a new transforming enumerator /// Owner; used to invoke the Transform() method /// Enumerator of the wrapped collection public TransformingEnumerator( TransformingReadOnlyCollection transformer, IEnumerator containedTypeEnumerator ) { this.transformer = transformer; 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 ExposedItemType Current { get { return this.transformer.Transform(this.containedTypeEnumerator.Current); } } /// 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; } } /// /// Collection that owns this enumerator; required to invoke the item /// transformation method. /// private TransformingReadOnlyCollection transformer; /// An enumerator from the wrapped collection private IEnumerator containedTypeEnumerator; } #endregion // class TransformingEnumerator /// Initializes a new transforming collection wrapper /// /// Internal list of items that are transformed into the exposed type when /// accessed through the TransformingReadOnlyCollection. /// public TransformingReadOnlyCollection(IList items) { this.items = items; } /// /// Determines whether an element is in the TransformingReadOnlyCollection /// /// /// The object to locate in the TransformingReadOnlyCollection. /// The value can be null for reference types. /// /// /// True if value is found in the TransformingReadOnlyCollection; 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(ExposedItemType item) { return (IndexOf(item) != -1); } /// /// Copies the entire TransformingReadOnlyCollection 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 TransformingReadOnlyCollection. 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 TransformingReadOnlyCollection 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(ExposedItemType[] 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] = Transform(this.items[itemIndex]); } /// /// Returns an enumerator that iterates through the TransformingReadOnlyCollection. /// /// /// An enumerator or the TransformingReadOnlyCollection. /// public IEnumerator GetEnumerator() { return new TransformingEnumerator(this, this.items.GetEnumerator()); } /// /// Searches for the specified object and returns the zero-based index of the /// first occurrence within the entire TransformingReadOnlyCollection. /// /// /// The object to locate in the TransformingReadOnlyCollection. The value can /// be null for reference types. /// /// /// The zero-based index of the first occurrence of item within the entire /// TransformingReadOnlyCollection, 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(ExposedItemType item) { if(item == null) { for(int index = 0; index < this.items.Count; ++index) { if(Transform(this.items[index]) == null) { return index; } } } else { EqualityComparer comparer = EqualityComparer.Default; for(int index = 0; index < this.items.Count; ++index) { if(comparer.Equals(Transform(this.items[index]), item)) { return index; } } } return -1; } /// /// The number of elements contained in the TransformingReadOnlyCollection 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 /// TransformingReadOnlyCollection.Count. /// public ExposedItemType this[int index] { get { return Transform(this.items[index]); } } /// Transforms an item into the exposed type /// Item to be transformed /// The transformed item /// /// This method is used to transform an item in the wrapped collection into /// the exposed item type whenever the user accesses an item. Expect it to /// be called frequently, because the TransformingReadOnlyCollection does /// not cache or otherwise store the transformed items. /// protected abstract ExposedItemType Transform(ContainedItemType item); #region IList Members /// /// Determines the index of a specific item in the TransformingReadOnlyCollection. /// /// /// The object to locate in the TransformingReadOnlyCollection. /// /// /// The index of item if found in the list; otherwise, -1. /// int IList.IndexOf(ExposedItemType item) { return IndexOf(item); } /// /// Inserts an item to the TransformingReadOnlyCollection at the specified index. /// /// /// The zero-based index at which item should be inserted. /// /// /// The object to insert into the TransformingReadOnlyCollection /// /// /// The TransformingReadOnlyCollection is read-only. /// /// /// index is not a valid index in the TransformingReadOnlyCollection. /// void IList.Insert(int index, ExposedItemType item) { throw new NotSupportedException("The collection is ready-only"); } /// /// Removes the TransformingReadOnlyCollection item at the specified index. /// /// The zero-based index of the item to remove. /// /// The TransformingReadOnlyCollection is read-only. /// /// /// Index is not a valid index in the TransformingReadOnlyCollection. /// void IList.RemoveAt(int index) { throw new NotSupportedException("The collection is ready-only"); } /// Gets or sets the element at the specified index. /// The zero-based index of the element to get or set. /// The element at the specified index. /// /// Index is not a valid index in the TransformingReadOnlyCollection. /// /// /// The property is set and the TransformingReadOnlyCollection is read-only /// ExposedItemType IList.this[int index] { get { return this[index]; } set { throw new NotSupportedException("The collection is ready-only"); } } #endregion #region ICollection Members /// Adds an item to the TransformingReadOnlyCollection. /// The object to add to the TransformingReadOnlyCollection /// /// The TransformingReadOnlyCollection is read-only. /// void ICollection.Add(ExposedItemType item) { throw new NotSupportedException("The collection is ready-only"); } /// Removes all items from the TransformingReadOnlyCollection /// /// The TransformingReadOnlyCollection is read-only. /// void ICollection.Clear() { throw new NotSupportedException("The collection is ready-only"); } /// /// Determines whether the TransformingReadOnlyCollection contains a specific value. /// /// /// The object to locate in the TransformingReadOnlyCollection. /// /// /// True if item is found in the TransformingReadOnlyCollection; otherwise, false. /// bool ICollection.Contains(ExposedItemType item) { return Contains(item); } /// /// Copies the elements of the TransformingReadOnlyCollection to an System.Array, /// starting at a particular System.Array index. /// /// /// The one-dimensional System.Array that is the destination of the elements /// copied from TransformingReadOnlyCollection. The System.Array must have /// zero-based indexing. /// /// /// The zero-based index in array at which copying begins /// /// /// ArrayIndex is less than 0. /// /// /// Array is null. /// /// /// Array is multidimensional or arrayIndex is equal to or greater than the /// length of array or the number of elements in the source /// TransformingReadOnlyCollection is greater than the available /// space from arrayIndex to the end of the destination array or type T cannot /// be cast automatically to the type of the destination array. /// void ICollection.CopyTo(ExposedItemType[] array, int arrayIndex) { CopyTo(array, arrayIndex); } /// /// Removes the first occurrence of a specific object from the /// TransformingReadOnlyCollection. /// /// /// The object to remove from the TransformingReadOnlyCollection /// /// /// True if item was successfully removed from the TransformingReadOnlyCollection; /// otherwise, false. This method also returns false if item is not found in the /// original TransformingReadOnlyCollection. /// /// /// The TransformingReadOnlyCollection is read-only. /// bool ICollection.Remove(ExposedItemType item) { throw new NotSupportedException("The collection is ready-only"); } /// /// The number of elements contained in the TransformingReadOnlyCollection. /// int ICollection.Count { get { return Count; } } /// /// A value indicating whether the TransformingReadOnlyCollection is read-only. /// bool ICollection.IsReadOnly { get { return true; } } #endregion #region IEnumerable Members /// Returns an enumerator that iterates through the collection. /// /// A System.Collections.Generic.IEnumerator<ExposedItemType> that can be used /// to iterate through the collection. /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region IEnumerable Members /// Returns an enumerator that iterates through a collection. /// /// A System.Collections.IEnumerator object that can be used to iterate through /// the collection. /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region IList Members /// Adds an item to the TransformingReadOnlyCollection. /// /// The System.Object to add to the TransformingReadOnlyCollection. /// /// The position into which the new element was inserted. /// /// The System.Collections.IList is read-only or the TransformingReadOnlyCollection /// has a fixed size. /// int IList.Add(object value) { throw new NotSupportedException("The collection is ready-only"); } /// Removes all items from the TransformingReadOnlyCollection. /// /// The TransformingReadOnlyCollection is read-only. /// void IList.Clear() { throw new NotSupportedException("The collection is ready-only"); } /// /// Determines whether the TransformingReadOnlyCollection contains a specific value. /// /// /// The System.Object to locate in the TransformingReadOnlyCollection. /// /// /// True if the System.Object is found in the TransformingReadOnlyCollection; /// otherwise, false. /// bool IList.Contains(object value) { return Contains((ExposedItemType)value); } /// /// Determines the index of a specific item in the TransformingReadOnlyCollection. /// /// /// The System.Object to locate in the TransformingReadOnlyCollection. /// /// /// The index of value if found in the list; otherwise, -1. /// int IList.IndexOf(object value) { return IndexOf((ExposedItemType)value); } /// /// Inserts an item to the TransformingReadOnlyCollection at the specified index. /// /// /// The zero-based index at which value should be inserted. /// /// /// The System.Object to insert into the TransformingReadOnlyCollection. /// /// /// Index is not a valid index in the TransformingReadOnlyCollection. /// /// /// The System.Collections.IList is read-only or the TransformingReadOnlyCollection /// has a fixed size. /// /// /// Value is null reference in the TransformingReadOnlyCollection. /// void IList.Insert(int index, object value) { throw new NotSupportedException("The collection is ready-only"); } /// /// A value indicating whether the TransformingReadOnlyCollection has a fixed /// size. /// bool IList.IsFixedSize { get { return true; } } /// /// A value indicating whether the index is not a valid index in the is read-only. /// bool IList.IsReadOnly { get { return true; } } /// /// Removes the first occurrence of a specific object from the /// TransformingReadOnlyCollection. /// /// /// The System.Object to remove from the TransformingReadOnlyCollection. /// /// /// The TransformingReadOnlyCollection is read-only or the /// TransformingReadOnlyCollection has a fixed size. /// void IList.Remove(object value) { throw new NotSupportedException("The collection is ready-only"); } /// /// Removes the TransformingReadOnlyCollection item at the specified index. /// /// The zero-based index of the item to remove. /// /// Index is not a valid index in the TransformingReadOnlyCollection. /// /// /// The TransformingReadOnlyCollection is read-only or the /// TransformingReadOnlyCollection has a fixed size. /// void IList.RemoveAt(int index) { throw new NotSupportedException("The collection is ready-only"); } /// Gets or sets the element at the specified index. /// The zero-based index of the element to get or set. /// The element at the specified index /// /// Index is not a valid index in the TransformingReadOnlyCollection /// /// /// The property is set and the TransformingReadOnlyCollection is read-only. /// object IList.this[int index] { get { return this[index]; } set { throw new NotSupportedException("The collection is ready-only"); } } #endregion #region ICollection Members /// /// Copies the elements of the TransformingReadOnlyCollection to an System.Array, /// starting at a particular System.Array index. /// /// /// The one-dimensional System.Array that is the destination of the elements /// copied from TransformingReadOnlyCollection. The System.Array must have zero-based /// indexing. /// /// The zero-based index in array at which copying begins. /// /// Array is null. /// /// /// Index is less than zero. /// /// /// Array is multidimensional or index is equal to or greater than the length /// of array or the number of elements in the source TransformingReadOnlyCollection /// is greater than the available space from index to the end of the destination /// array. /// /// /// The type of the source TransformingReadOnlyCollection cannot be cast /// automatically to the type of the destination array. /// void ICollection.CopyTo(Array array, int index) { CopyTo((ExposedItemType[])array, index); } /// /// The number of elements contained in the TransformingReadOnlyCollection. /// int ICollection.Count { get { return Count; } } /// /// A value indicating whether access to the TransformingReadOnlyCollection /// is synchronized (thread safe). /// bool ICollection.IsSynchronized { get { return false; } } /// /// An object that can be used to synchronize access to the /// TransformingReadOnlyCollection. /// object ICollection.SyncRoot { get { if(this.syncRoot == null) { ICollection is2 = this.items as ICollection; if(is2 != null) { this.syncRoot = is2.SyncRoot; } else { Interlocked.CompareExchange(ref this.syncRoot, new object(), null); } } return this.syncRoot; } } #endregion /// Items being transformed upon exposure by this collection private IList items; /// Synchronization root for threaded accesses to this collection private object syncRoot; } } // namespace Nuclex.Support.Collections