/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * 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. * */ using System; using System.Collections; using System.Collections.Generic; namespace QuantConnect.Util { /// /// Defines an enumerable that can be enumerated many times while /// only performing a single enumeration of the root enumerable /// /// public class MemoizingEnumerable : IEnumerable { private List _buffer; private IEnumerator _enumerator; /// /// Allow disableing the buffering /// /// Should be called before the enumeration starts public bool Enabled { get; set; } /// /// Gets the count of items in the enumerable. This will force enumeration of the entire collection if it has not already been enumerated. /// public int Count { get { if (!Enabled) { throw new InvalidOperationException("Count is not supported when memoization is disabled"); } if (_buffer == null) { foreach (var item in this) {} } return _buffer.Count; } } /// /// Gets whether the enumerable is empty. This will force enumeration of the first item if it has not already been enumerated. /// public bool Empty { get { if (!Enabled) { throw new InvalidOperationException("Empty is not supported when memoization is disabled"); } if (_buffer == null || _buffer.Count == 0) { using var enumerator = GetEnumerator(); return !enumerator.MoveNext(); } return _buffer.Count == 0; } } /// /// Initializes a new instance of the class /// /// The source enumerable to be memoized public MemoizingEnumerable(IEnumerable enumerable) { Enabled = true; _enumerator = enumerable.GetEnumerator(); } /// /// Returns an enumerator that iterates through the collection. /// /// /// A that can be used to iterate through the collection. /// /// 1 public IEnumerator GetEnumerator() { if (!Enabled) { if (_enumerator != null) { while (_enumerator.MoveNext()) { yield return _enumerator.Current; } // important to avoid leak! _enumerator.Dispose(); _enumerator = null; } } else { if (_buffer == null) { // lazy create our buffer _buffer = new List(); } int i = 0; while (i <= _buffer.Count) { // sync for multiple threads access to _enumerator and _buffer lock (_buffer) { // check to see if we need to move next if (_enumerator != null && i >= _buffer.Count) { if (_enumerator.MoveNext()) { var value = _enumerator.Current; _buffer.Add(value); yield return value; } else { // important to avoid leak! _enumerator.Dispose(); _enumerator = null; } } else { // we have a value if it's in the buffer if (_buffer.Count > i) { yield return _buffer[i]; } } } // increment for next time i++; } } } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate through the collection. /// /// 2 IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }