//-----------------------------------------------------------------------
//
// 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.
}
}
}
}
}