2012-08-11 82 views
3

迭代器块的延迟加载行为正在缓存数据中造成困难。想想这个小测试程序:使用缓存迭代器块结果的最佳做法

class Program 
{ 
    static IEnumerable<int> LoadDataFromDatabase() 
    { 
     Console.WriteLine("Hitting database...."); 
     yield return 13; 
    } 

    static IEnumerable<int> _cachedData = null; 
    static IEnumerable<int> CachedData 
    { 
     get 
     { 
      if (_cachedData == null) 
      { 
       _cachedData = LoadDataFromDatabase(); 
      } 
      return _cachedData; 
     } 
    } 

    static void Main(string[] args) 
    { 
     Console.WriteLine(string.Format("Collection contains {0} items.", CachedData.Count())); 
     Console.WriteLine(string.Format("Collection contains {0} items.", CachedData.Count())); 
    } 
} 

输出到这是

击中数据库....

集合包含1项。

打数据库....

集合包含1项。

我想只打一次数据库(因此缓存),但由于LoadDataFromDatabase()是一个迭代块,实际的数据库调用是什么缓存 - 而不是数据。

这种情况下的最佳做法是什么?我应该只是做_cachedData = LoadDataFromDatabase().ToList()来存储评估数据?

+2

您期望LoadDataFromDatabase'加载多少数据,它是否需要更改应用程序的生命周期?如果答案有点不变,那么为简单起见,您应该将数据存储为列表。 – 2012-08-11 09:52:33

+0

我可以假设,如果我缓存数据,我确实需要整个列表,因此评估整个数据集总是可以的。而且我确实有一套适用于污染数据的机制。我想知道是否'.ToList()'是一种解决懒惰评估的标准方法,或者如果还有更多的建议(例如,有一些属性我可以放在迭代器块方法中告诉C#不要懒惰评估)。 – tenfour 2012-08-11 09:54:47

+0

你想缓存smth不要命中数据库两次 - 正确吗? – 2012-08-11 09:59:57

回答

2

您可以添加.ToList()

static IEnumerable<int> CachedData 
{ 
    get 
    { 
     if (_cachedData == null) 
     { 
      _cachedData = LoadDataFromDatabase().ToList(); 
     } 
     return _cachedData; 
    } 
} 

的缺点是,如果在列表中的100.000项目,你这样做:

var list1 = CachedData.Take(2).Sum(); 
var list2 = CachedData.Take(3).Sum(); 
var list3 = CachedData.Take(1).Sum(); 

...这将加载100.000项目名单。

解决的办法是制作一个LazyList缓存可枚举数您正在迭代,而不是提前。只需将.ToList()替换为.ToLazyList()即可。

这导致最佳的选择:

  • 第一3项仅加载总共1次。
  • 项目4从未加载

一个implementation of a lazy list is here的一个例子。

+0

我认为你应该保持简单,就像你在答案的第一部分所做的一样。如果表现存在问题,他应该只考虑替代解决方案。 – 2013-10-12 21:25:08

+0

KISS是一个我非常喜欢的原则,它不会经常发生,每个循环都使用2个嵌套的迭代器。这是第一个简单解决方案的很好的理由。然而,在某些情况下这是不正确的,所以我做了一个更复杂的版本,在所有(单线程)情况下都能正确工作。你可以选择你的owm实现。 – 2013-10-13 07:35:38