21
public IEnumerable<ModuleData> ListModules()
{
    foreach (XElement m in Source.Descendants("Module"))
    {
        yield return new ModuleData(m.Element("ModuleID").Value);
    }
}

最初は、コレクション全体を評価する必要がないため、上記のコードは優れています。

ただし、すべてのモジュールが 1 回列挙されると、変更がない場合に 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;
}

これは、リスト全体を繰り返し使用する場合には優れていますが、それ以外の場合はそれほど優れていません。

リスト全体が繰り返されるまでリターンを生成し、それをキャッシュして後続のリクエストにキャッシュを提供できる中間点はありますか?

4

7 に答える 7

8

レイジーリスト(一度反復されたアイテムをキャッシュする)を作成する方法を説明している列挙子の状態の保存を見ることができます。

于 2009-10-08T11:09:52.790 に答える
7

Reactive Extensions for .NETライブラリ(Rx)を確認してくださいMemoizeAll()。それは怠惰に評価されるので、建設中に安全にセットアップして、そこから戻ることができます:ModulesListModules()

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

MemoizeAll()ここに(そして他のあまり目立たないRx拡張機能のいくつか)の良い説明があります

于 2010-11-03T09:03:44.347 に答える
7

@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から に 1 つのアイテムを取得_enumerator_cacheます。
  3. xfor2 番目のループの前に一時停止します。
  4. 内側のループ ( y) は、 から 1 つの要素を列挙し_cacheます。
  5. yから までのすべての要素を取得し_enumeratorます_cache
  6. y変数が に等しいforため、3 番目のループをスキップします。index5
  7. x再開、そのindex等号1。終了しforたため、2 番目のループをスキップします。_enumerator
  8. x3 番目のループ _cacheを使用して1 つの要素を列挙します。for
  9. x3 番目の前に一時停止しforます。
  10. y_cacheusing 最初のforループから 5 つの要素を列挙します。
  11. y終了しforたため、2 番目のループをスキップします。_enumerator
  12. yequalsのため、 3 番目のforループをスキップします。indexy5
  13. x再開し、増加しますindex。3 番目のループ_cacheを使用して1 つの要素をフェッチします。for
  14. x一時停止します。
  15. indexの変数xがより小さい場合は105に進みます。
  16. 終わり。
于 2016-01-06T12:42:37.013 に答える
5

私はいくつかの実装を見てきましたが、古いものや最新の .Net クラスを利用していないもの、私のニーズには手の込んだものもありました。最終的に、まとめることができる最も簡潔で宣言的なコードになりました。これを合計すると、約 15 行の (実際の) コードを持つクラスになりました。OPのニーズとうまく一致しているようです:

編集: 2 番目のリビジョン、空の列挙型のより良いサポート

/// <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;
  }
}
于 2016-01-06T11:36:29.330 に答える
1

少し要約すると、次のようになります。

  • この回答では、簡単に使用できる拡張メソッドと単体テストを備えたソリューションが提示されています。ただし、再帰を使用するため、割り当てが少ないため、他の非再帰ソリューションよりもパフォーマンスが低下することが予想されます。
  • この回答では、列挙可能なものが2回列挙されている場合を説明するためのコードを含む、非再帰的なソリューションが提示されています。ただし、この状況では、元の列挙可能な順序が維持されない可能性があり、2 つを超える同時列挙にスケーリングされません。
  • この回答では、元の列挙型の順序を維持しながら、複数の同時列挙の場合のソリューションを一般化するために、列挙子メソッドが書き直されています。

すべての回答のコードを組み合わせると、次のクラスが得られます。このコードはスレッド セーフではないことに注意してください。つまり、同時列挙は同じスレッドからのみ安全です。

public class CachedEnumerable<T> : IEnumerable<T>, IDisposable
{
    private readonly IEnumerator<T> enumerator;
    private readonly List<T> cache = new List<T>();

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

    public CachedEnumerable(IEnumerator<T> enumerator)
        => this.enumerator = enumerator ?? throw new ArgumentNullException(nameof(enumerator));

    public IEnumerator<T> GetEnumerator()
    {
        int index = 0;
        while (true) {
            if (index < cache.Count) {
                yield return cache[index];
                index++;
            }
            else if (enumerator.MoveNext())
                cache.Add(enumerator.Current);
            else
                yield break;
        }
    }

    public void Dispose() => enumerator.Dispose();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

簡単に使用できる静的拡張メソッドを使用すると、次のようになります。

public static class EnumerableUtils
{
    public static CachedEnumerable<T> ToCachedEnumerable<T>(this IEnumerable<T> enumerable) 
        => new CachedEnumerable<T>(enumerable);
}

対応する単体テスト:

public class CachedEnumerableTest
{
    private int _count;

    [Test]
    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);
    }

    [Test]
    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]
    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.That(_count <= 1);
    }

    [Test]
    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;
    }
}
于 2020-06-25T15:09:54.437 に答える
-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;
}
于 2009-10-08T11:12:39.513 に答える