//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // This code is released under the Microsoft Public License (Ms-PL). // //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging { using System; using System.Collections; using System.Collections.Generic; using Validation; /// /// Extension methods for types. /// public static class EnumerableCacheExtensions { /// /// Caches the results of enumerating over a given object so that subsequence enumerations /// don't require interacting with the object a second time. /// /// The type of element found in the enumeration. /// The enumerable object. /// /// Either a new enumerable object that caches enumerated results, or the original, /// object if no caching is necessary to avoid additional CPU work. /// /// /// This is designed for use on the results of generator methods (the ones with yield return in them) /// so that only those elements in the sequence that are needed are ever generated, while not requiring /// regeneration of elements that are enumerated over multiple times. /// This can be a huge performance gain if enumerating multiple times over an expensive generator method. /// Some enumerable types such as collections, lists, and already-cached generators do not require /// any (additional) caching, and this method will simply return those objects rather than caching them /// to avoid double-caching. /// public static IEnumerable CacheGeneratedResults(this IEnumerable sequence) { Requires.NotNull(sequence, "sequence"); // Don't create a cache for types that don't need it. if (sequence is IList || sequence is ICollection || sequence is Array || sequence is EnumerableCache) { return sequence; } return new EnumerableCache(sequence); } /// /// A wrapper for types and returns a caching /// from its method. /// /// The type of element in the sequence. private class EnumerableCache : IEnumerable { /// /// The results from enumeration of the live object that have been collected thus far. /// private List cache; /// /// The original generator method or other enumerable object whose contents should only be enumerated once. /// private IEnumerable generator; /// /// The enumerator we're using over the generator method's results. /// private IEnumerator generatorEnumerator; /// /// The sync object our caching enumerators use when adding a new live generator method result to the cache. /// /// /// Although individual enumerators are not thread-safe, this should be /// thread safe so that multiple enumerators can be created from it and used from different threads. /// private object generatorLock = new object(); /// /// Initializes a new instance of the EnumerableCache class. /// /// The generator. internal EnumerableCache(IEnumerable generator) { Requires.NotNull(generator, "generator"); this.generator = generator; } #region IEnumerable Members /// /// Returns an enumerator that iterates through the collection. /// /// /// A that can be used to iterate through the collection. /// public IEnumerator GetEnumerator() { if (this.generatorEnumerator == null) { this.cache = new List(); this.generatorEnumerator = this.generator.GetEnumerator(); } return new EnumeratorCache(this); } #endregion #region IEnumerable Members /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate through the collection. /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } #endregion /// /// An enumerator that uses cached enumeration results whenever they are available, /// and caches whatever results it has to pull from the original object. /// private class EnumeratorCache : IEnumerator { /// /// The parent enumeration wrapper class that stores the cached results. /// private EnumerableCache parent; /// /// The position of this enumerator in the cached list. /// private int cachePosition = -1; /// /// Initializes a new instance of the EnumeratorCache class. /// /// The parent cached enumerable whose GetEnumerator method is calling this constructor. internal EnumeratorCache(EnumerableCache parent) { Requires.NotNull(parent, "parent"); this.parent = parent; } #region IEnumerator Members /// /// Gets the element in the collection at the current position of the enumerator. /// /// /// The element in the collection at the current position of the enumerator. /// public T Current { get { if (this.cachePosition < 0 || this.cachePosition >= this.parent.cache.Count) { throw new InvalidOperationException(); } return this.parent.cache[this.cachePosition]; } } #endregion #region IEnumerator Properties /// /// Gets the element in the collection at the current position of the enumerator. /// /// /// The element in the collection at the current position of the enumerator. /// object System.Collections.IEnumerator.Current { get { return this.Current; } } #endregion #region IDisposable Members /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } #endregion #region IEnumerator Methods /// /// Advances the enumerator to the next element of the collection. /// /// /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. /// /// /// The collection was modified after the enumerator was created. /// public bool MoveNext() { this.cachePosition++; if (this.cachePosition >= this.parent.cache.Count) { lock (this.parent.generatorLock) { if (this.parent.generatorEnumerator.MoveNext()) { this.parent.cache.Add(this.parent.generatorEnumerator.Current); } else { return false; } } } return true; } /// /// 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.cachePosition = -1; } #endregion /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { // Nothing to do here. } } } } }