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

प्रारंभ में उपर्युक्त कोड बहुत अच्छा है क्योंकि यदि आवश्यक नहीं है तो पूरे संग्रह का मूल्यांकन करने की आवश्यकता नहीं है।कैशिंग आईन्यूमेरेबल

हालांकि, एक बार सभी मॉड्यूल एक बार प्रगणित किया गया है, इसे और अधिक महंगा हो जाता है बार-बार 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

यह बहुत कठिन है कि मेरा लेकिन कॉललिस्ट को कॉल करना() पूरी तरह से गणना करने योग्य है, इसलिए यह मेरी समस्या का समाधान नहीं करता है। – djskinner

4

Reactive Extensions for .NET पुस्तकालय (आरएक्स) में बाहर MemoizeAll() चेक। के रूप में यह मूल्यांकन किया जाता है lazily आप सुरक्षित रूप से यह निर्माण के दौरान सेट कर सकते हैं और सिर्फ ListModules() से Modules वापसी:

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

MemoizeAll() का एक अच्छा विवरण (और अन्य कम स्पष्ट आरएक्स एक्सटेंशन के कुछ) here नहीं है।

+0

यह बहुत अच्छा है, मुझे आरएक्स के उपयोग पसंद है। मैं अभी भी इसके साथ और अधिक अच्छी तरह से खेलने के लिए समय और बहाना खोजने का प्रयास कर रहा हूं। – djskinner

3

मैं वहां कार्यान्वयन के एक मुट्ठी भर देखा है, नवीनतम नेट वर्गों के कुछ पुराने और नहीं ले रही लाभ, कुछ भी मेरी जरूरतों के लिए विस्तार से बताएं। मैं सबसे संक्षिप्त और घोषणात्मक कोड के साथ समाप्त हुआ जो मैं कर सकता था, जो कि कक्षा में लगभग 15 लाइनों (वास्तविक) कोड के साथ जोड़ा गया था। , द्वितीय संशोधन एक उपयोगी विस्तार विधि हो सकता है खाली enumerables

/// <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 का जवाब पसंद है। लेकिन मैं अपने समाधान का प्रस्ताव देना चाहता हूं, जिसका एफपी से कोई लेना देना नहीं है। यह निष्पक्ष दृष्टिकोण है, लेकिन यह आवंटन के बहुत कम उत्पन्न करता है। और यह धागा सुरक्षित नहीं है।

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 फिर से शुरू होता है, इसकी index1 के बराबर होती है। यह दूसरे for पाश को छोड़ देता है क्योंकि _enumerator समाप्त हो गया है;
  8. x_cache से तीसरे for लूप का उपयोग करके एक तत्व को दर्शाता है;
  9. x तीसरे for से पहले रोकता है;
  10. y से 5 तत्वों को पहले for लूप का उपयोग करके समझाता है;
  11. y दूसरे for लूप को छोड़ देता है, क्योंकि _enumerator समाप्त हो गया है;
  12. y तीसरे for लूप को छोड़ देता है, क्योंकि indexy5 के बराबर है;
  13. x फिर से शुरू होता है, index में वृद्धि करता है। यह _cache से तीसरा for लूप का उपयोग करके एक तत्व प्राप्त करता है।
  14. x विराम।
  15. यदि indexx का चर 5 से कम है तो 10 पर जाएं;
  16. अंत।
+0

अच्छा और साफ, और मुझे यह भी पसंद है कि यह समाधान तत्काल – tsemer

+0

पर पहले आइटम की गणना नहीं करता है, साफ और सीधा दिखता है। कृपया आप एक स्पष्टीकरण जोड़ सकते हैं कि तीसरे 'ब्लॉक' की आवश्यकता क्यों है? – djskinner

+1

@djskinner मैंने कुछ जानकारी – hazzik

1

मुझे काफी हज़िक का जवाब पसंद है ... अच्छा और सरल हमेशा रास्ता है। लेकिन GetEnumerator

में एक बग है, यह समझता है कि कोई समस्या है, और यही कारण है कि दूसरे गणक लूप के बाद एक अजीब 3 पाश है .... लेकिन यह उतना आसान नहीं है। समस्या जो तीसरे पाश की आवश्यकता को ट्रिगर करती है सामान्य है ... इसलिए इसे रिकर्सिव होना चाहिए।

उत्तर हालांकि आसान दिखता है।

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; 
       } 
      } 
     } 
    } 

हाँ आप इसे एक छोटा सा और अधिक कुशल वर्तमान उपज द्वारा कर सकते हैं ... लेकिन मैं माइक्रोसेकंड हिट लेंगे ... यह केवल कभी प्रति तत्व एक बार होता है।

और यह थ्रेडसेफ नहीं है ... लेकिन इसकी परवाह कौन करता है।

संबंधित मुद्दे