#region CPL License /* Nuclex Framework Copyright (C) 2002-2013 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 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 partial class TransformingReadOnlyCollection< TContainedItem, TExposedItem > : 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 TExposedItem 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(TExposedItem 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(TExposedItem[] 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(TExposedItem item) { if(item == null) { for(int index = 0; index < this.items.Count; ++index) { if(Transform(this.items[index]) == null) { return index; } } } else { var 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 TExposedItem this[int index] { get { return Transform(this.items[index]); } } /// Whether the List is write-protected public bool IsReadOnly { get { return true; } } /// 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 TExposedItem Transform(TContainedItem item); /// 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