#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 {
/// 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