2009-10-08 4 views
17
public IEnumerable<ModuleData> ListModules() 
{ 
    foreach (XElement m in Source.Descendants("Module")) 
    { 
     yield return new ModuleData(m.Element("ModuleID").Value); 
    } 
} 

Первоначально вышеуказанный код замечательный, поскольку нет необходимости оценивать всю коллекцию, если она не нужна.Кэширование IEnumerable

Однако, как только все Модули были перечислены один раз, становится все дороже повторять запрос XDocument, когда нет изменений.

Таким образом, как повышение производительности:

public IEnumerable<ModuleData> ListModules() 
{ 
    if (Modules == null) 
    { 
     Modules = new List<ModuleData>(); 
     foreach (XElement m in Source.Descendants("Module")) 
     { 
      Modules.Add(new ModuleData(m.Element("ModuleID").Value, 1, 1)); 
     } 
    } 
    return Modules; 
} 

это здорово, если я несколько раз, используя весь список, но не настолько велик, в противном случае.

Есть ли промежуточная площадка, где я могу дать доход до тех пор, пока весь список не будет повторен, затем кешируйте его и обслуживайте кеш для последующих запросов?

+1

Я получаю sth. неправильно? Ваш код, похоже, делает именно то, о чем вы просите ... –

+1

Второй блок кода всегда будет перебирать все перечислимое, даже если это не требуется. – djskinner

ответ

8

Вы можете посмотреть Saving the State of Enumerators, в котором описано, как создать ленивый список (который кэшируется после итерации).

+0

очень круто! спасибо за ссылку, которая полностью решена аналогичной проблемой, с которой я столкнулся с запросом, который читается с диска. – luke

+0

Для потомков вы могли бы включить соответствующие части ссылки, которые были сочтены полезными в вашем ответе? Таким образом, если ссылка опустится, изменения и т. Д., Ваш ответ не будет бесполезен. Большое спасибо. –

-1

Я не вижу серьезных проблем с идеей кэширования результатов в списке, как в приведенном выше коде. Вероятно, было бы лучше построить список, используя метод ToList().

public IEnumerable<ModuleData> ListModules() 
{ 
    if (Modules == null) 
    { 
     Modules = Source.Descendants("Module") 
         .Select(m => new ModuleData(m.Element("ModuleID").Value, 1, 1))) 
         .ToList(); 
    } 
    return Modules; 
} 
+0

Это намного более аккуратно, что моя, но вызывающая ToList() выполняет итерацию по всему перечислимому так, чтобы она не решила мою проблему. – djskinner

4

MemoizeAll() Заканчивать в Reactive Extensions for .NET библиотеке (Rx). Как оценивается лениво вы можете спокойно установить его во время строительства и просто вернуть ModulesListModules() из:

Modules = Source. 
    Descendants("Module"). 
    Select(m => new ModuleData(m.Element("ModuleID").Value, 1, 1)). 
    MemoizeAll(); 

Там хорошее объяснение MemoizeAll() (и некоторых других менее очевидных расширений Rx) here.

+0

Это очень приятно, мне нравится использование Rx. Я все еще пытаюсь найти время и оправдание, чтобы поиграть с ним более тщательно. – djskinner

3

Я видел несколько реализаций там, некоторые старше и не пользуясь новейшими .Net-классами, некоторые из которых слишком сложны для моих нужд. Я закончил с самым кратким и декларативным кодом, который я смог собрать, который добавился к классу с примерно 15 строками (фактического) кода. Это, кажется, хорошо согласуются с потребностями Op в:

Edit: Второй пересмотр, лучшую поддержку для пустых перечислимых

/// <summary> 
/// A <see cref="IEnumerable{T}"/> that caches every item upon first enumeration. 
/// </summary> 
/// <seealso cref="http://blogs.msdn.com/b/matt/archive/2008/03/14/digging-deeper-into-lazy-and-functional-c.aspx"/> 
/// <seealso cref="http://blogs.msdn.com/b/wesdyer/archive/2007/02/13/the-virtues-of-laziness.aspx"/> 
public class CachedEnumerable<T> : IEnumerable<T> { 
    private readonly bool _hasItem; // Needed so an empty enumerable will not return null but an actual empty enumerable. 
    private readonly T _item; 
    private readonly Lazy<CachedEnumerable<T>> _nextItems; 

    /// <summary> 
    /// Initialises a new instance of <see cref="CachedEnumerable{T}"/> using <paramref name="item"/> as the current item 
    /// and <paramref name="nextItems"/> as a value factory for the <see cref="CachedEnumerable{T}"/> containing the next items. 
    /// </summary> 
    protected internal CachedEnumerable(T item, Func<CachedEnumerable<T>> nextItems) { 
    _hasItem = true; 
    _item = item; 
    _nextItems = new Lazy<CachedEnumerable<T>>(nextItems); 
    } 

    /// <summary> 
    /// Initialises a new instance of <see cref="CachedEnumerable{T}"/> with no current item and no next items. 
    /// </summary> 
    protected internal CachedEnumerable() { 
    _hasItem = false; 
    } 

    /// <summary> 
    /// Instantiates and returns a <see cref="CachedEnumerable{T}"/> for a given <paramref name="enumerable"/>. 
    /// Notice: The first item is always iterated through. 
    /// </summary> 
    public static CachedEnumerable<T> Create(IEnumerable<T> enumerable) { 
    return Create(enumerable.GetEnumerator()); 
    } 

    /// <summary> 
    /// Instantiates and returns a <see cref="CachedEnumerable{T}"/> for a given <paramref name="enumerator"/>. 
    /// Notice: The first item is always iterated through. 
    /// </summary> 
    private static CachedEnumerable<T> Create(IEnumerator<T> enumerator) { 
    return enumerator.MoveNext() ? new CachedEnumerable<T>(enumerator.Current,() => Create(enumerator)) : new CachedEnumerable<T>(); 
    } 

    /// <summary> 
    /// Returns an enumerator that iterates through the collection. 
    /// </summary> 
    public IEnumerator<T> GetEnumerator() { 
    if (_hasItem) { 
     yield return _item; 

     var nextItems = _nextItems.Value; 
     if (nextItems != null) { 
     foreach (var nextItem in nextItems) { 
      yield return nextItem; 
     } 
     } 
    } 
    } 

    /// <summary> 
    /// Returns an enumerator that iterates through a collection. 
    /// </summary> 
    IEnumerator IEnumerable.GetEnumerator() { 
    return GetEnumerator(); 
    } 
} 

может быть полезным метод расширения:

public static class IEnumerableExtensions { 
    /// <summary> 
    /// Instantiates and returns a <see cref="CachedEnumerable{T}"/> for a given <paramref name="enumerable"/>. 
    /// Notice: The first item is always iterated through. 
    /// </summary> 
    public static CachedEnumerable<T> ToCachedEnumerable<T>(this IEnumerable<T> enumerable) { 
    return CachedEnumerable<T>.Create(enumerable); 
    } 
} 

И для блока тестеры среди вас: (если вы не используете resharper, просто выньте атрибуты [SuppressMessage])

/// <summary> 
/// Tests the <see cref="CachedEnumerable{T}"/> class. 
/// </summary> 
[TestFixture] 
public class CachedEnumerableTest { 
    private int _count; 

    /// <remarks> 
    /// This test case is only here to emphasise the problem with <see cref="IEnumerable{T}"/> which <see cref="CachedEnumerable{T}"/> attempts to solve. 
    /// </remarks> 
    [Test] 
    [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] 
    [SuppressMessage("ReSharper", "ReturnValueOfPureMethodIsNotUsed")] 
    public void MultipleEnumerationAreNotCachedForOriginalIEnumerable() { 
    _count = 0; 

    var enumerable = Enumerable.Range(1, 40).Select(IncrementCount); 

    enumerable.Take(3).ToArray(); 
    enumerable.Take(10).ToArray(); 
    enumerable.Take(4).ToArray(); 

    Assert.AreEqual(17, _count); 
    } 

    /// <remarks> 
    /// This test case is only here to emphasise the problem with <see cref="IList{T}"/> which <see cref="CachedEnumerable{T}"/> attempts to solve. 
    /// </remarks> 
    [Test] 
    [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] 
    [SuppressMessage("ReSharper", "ReturnValueOfPureMethodIsNotUsed")] 
    public void EntireListIsEnumeratedForOriginalListOrArray() { 
    _count = 0; 
    Enumerable.Range(1, 40).Select(IncrementCount).ToList(); 
    Assert.AreEqual(40, _count); 

    _count = 0; 
    Enumerable.Range(1, 40).Select(IncrementCount).ToArray(); 
    Assert.AreEqual(40, _count); 
    } 

    [Test] 
    [SuppressMessage("ReSharper", "ReturnValueOfPureMethodIsNotUsed")] 
    public void MultipleEnumerationsAreCached() { 
    _count = 0; 

    var cachedEnumerable = Enumerable.Range(1, 40).Select(IncrementCount).ToCachedEnumerable(); 

    cachedEnumerable.Take(3).ToArray(); 
    cachedEnumerable.Take(10).ToArray(); 
    cachedEnumerable.Take(4).ToArray(); 

    Assert.AreEqual(10, _count); 
    } 

    [Test] 
    public void FreshCachedEnumerableDoesNotEnumerateExceptFirstItem() { 
    _count = 0; 

    Enumerable.Range(1, 40).Select(IncrementCount).ToCachedEnumerable(); 

    Assert.AreEqual(1, _count); 
    } 

    /// <remarks> 
    /// Based on Jon Skeet's test mentioned here: http://www.siepman.nl/blog/post/2013/10/09/LazyList-A-better-LINQ-result-cache-than-List.aspx 
    /// </remarks> 
    [Test] 
    [SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")] 
    public void MatrixEnumerationIteratesAsExpectedWhileStillKeepingEnumeratedValuesCached() { 
    _count = 0; 

    var cachedEnumerable = Enumerable.Range(1, 5).Select(IncrementCount).ToCachedEnumerable(); 

    var matrixCount = 0; 

    foreach (var x in cachedEnumerable) { 
     foreach (var y in cachedEnumerable) { 
     matrixCount++; 
     } 
    } 

    Assert.AreEqual(5, _count); 
    Assert.AreEqual(25, matrixCount); 
    } 

    [Test] 
    public void OrderingCachedEnumerableWorksAsExpectedWhileStillKeepingEnumeratedValuesCached() { 
    _count = 0; 

    var cachedEnumerable = Enumerable.Range(1, 5).Select(IncrementCount).ToCachedEnumerable(); 

    var orderedEnumerated = cachedEnumerable.OrderBy(x => x); 
    var orderedEnumeratedArray = orderedEnumerated.ToArray(); // Enumerated first time in ascending order. 
    Assert.AreEqual(5, _count); 

    for (int i = 0; i < orderedEnumeratedArray.Length; i++) { 
     Assert.AreEqual(i + 1, orderedEnumeratedArray[i]); 
    } 

    var reorderedEnumeratedArray = orderedEnumerated.OrderByDescending(x => x).ToArray(); // Enumerated second time in descending order. 
    Assert.AreEqual(5, _count); 

    for (int i = 0; i < reorderedEnumeratedArray.Length; i++) { 
     Assert.AreEqual(5 - i, reorderedEnumeratedArray[i]); 
    } 
    } 

    private int IncrementCount(int value) { 
    _count++; 
    return value; 
    } 
} 
2

Мне нравится ответ @ tsemer. Но я хотел бы предложить свои решения, которые не имеют ничего общего с FP. Это наивный подход, но он генерирует намного меньше ассигнований. И не является потокобезопасным.

public class CachedEnumerable<T> : IEnumerable<T>, IDisposable 
{ 
    IEnumerator<T> _enumerator; 
    readonly List<T> _cache = new List<T>(); 

    public CachedEnumerable(IEnumerable<T> enumerable) 
     : this(enumerable.GetEnumerator()) 
    { 
    } 

    public CachedEnumerable(IEnumerator<T> enumerator) 
    { 
     _enumerator = enumerator; 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     // The index of the current item in the cache. 
     int index = 0; 

     // Enumerate the _cache first 
     for (; index < _cache.Count; index++) 
     { 
      yield return _cache[index]; 
     } 

     // Continue enumeration of the original _enumerator, 
     // until it is finished. 
     // This adds items to the cache and increment 
     for (; _enumerator != null && _enumerator.MoveNext(); index++) 
     { 
      var current = _enumerator.Current; 
      _cache.Add(current); 
      yield return current; 
     } 

     if (_enumerator != null) 
     { 
      _enumerator.Dispose(); 
      _enumerator = null; 
     } 

     // Some other users of the same instance of CachedEnumerable 
     // can add more items to the cache, 
     // so we need to enumerate them as well 
     for (; index < _cache.Count; index++) 
     { 
      yield return _cache[index]; 
     } 
    } 

    public void Dispose() 
    { 
     if (_enumerator != null) 
     { 
      _enumerator.Dispose(); 
      _enumerator = null; 
     } 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 

Это как тест матрицы от @ tsemer Ответим будет работать:

var ints = new [] { 1, 2, 3, 4, 5 }; 
var cachedEnumerable = new CachedEnumerable<int>(ints); 
foreach (var x in cachedEnumerable) 
{ 
    foreach (var y in cachedEnumerable) 
    { 
     //Do something 
    } 
} 
  1. Внешний контур (x) пропускает первый for, потому что _cache пуст;
  2. x извлекает один элемент из _enumerator в _cache;
  3. x паузы перед вторым for петля;
  4. Внутренний контур (y) перечисляет один элемент из _cache;
  5. y извлекает все элементы из _enumerator в _cache;
  6. y пропускает третий цикл for, поскольку его переменная index равна 5;
  7. x резюме, его index всего: 1. Он пропускает второй цикл for, потому что _enumerator закончен;
  8. x перечисляет один элемент из _cache с использованием третьего цикла for;
  9. x паузы до третьего for;
  10. y перечисляет 5 элементов из _cache с использованием первых for петли;
  11. y пропускает второй цикл for, потому что _enumerator закончен;
  12. y пропускает третий цикл for, поскольку index от y равно 5;
  13. x возобновляется, увеличивается index. Он извлекает один элемент из _cache с использованием третьего цикла for.
  14. x паузы.
  15. если index переменная x составляет менее 5, тогда перейдите на 10;
  16. конец.
+0

Приятный и чистый, и мне также нравится, что это решение не перечисляет первый элемент при создании экземпляра – tsemer

+0

Выглядит чисто и просто. Не могли бы вы добавить объяснение, почему нужен третий блок 'for'? – djskinner

+1

@djskinner Я добавил некоторую информацию – hazzik

1

Мне очень нравится ответ хазика ... приятно и просто всегда так. BUT есть ошибка в GetEnumerator

Это похоже на проблему, и вот почему есть странный третий цикл после 2-го цикла перечислителя .... но это не так просто. Проблема, которая вызывает необходимость в третьем цикле, является общей ... поэтому она должна быть рекурсивной.

Ответ, хотя выглядит еще проще.

public IEnumerator<T> GetEnumerator() 
    { 
     int index = 0; 

     while (true) 
     { 
      if (index < _cache.Count) 
      { 
       yield return _cache[index]; 
       index = index + 1; 
      } 
      else 
      { 
       if (_enumerator.MoveNext()) 
       { 
        _cache.Add(_enumerator.Current); 
       } 
       else 
       { 
        yield break; 
       } 
      } 
     } 
    } 

да, вы можете сделать его чуть-чуть более эффективным, давая ток ... но я возьму микросекунды хит ... это только когда-либо случается один раз на каждый элемент.

и его не резьбовое ... но кто заботится об этом.

Смежные вопросы