是否有一个IEnumerable实现,只有在它的迭代源(例如LINQ)一次 [英] Is there an IEnumerable implementation that only iterates over it's source (e.g. LINQ) once

查看:155
本文介绍了是否有一个IEnumerable实现,只有在它的迭代源(例如LINQ)一次的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

提供的项目为q LINQ表达式的结果:

Provided items is the result of q LINQ expression:

var items = from item in ItemsSource.RetrieveItems()
            where ...

假设代。每个项目的需要一些非negligeble时间

Suppose generation of each item takes some non-negligeble time.

两种操作模式是可能的:

Two modes of operation are possible:

使用的foreach 将允许开始集合的开始与项目合作更快的比中端变得可用。但是,如果我们想稍后再处理相同的集合,我们就必须复制保存:

  1. Using foreach would allow to start working with items in the beginning of the collection much sooner than whose in the end become available. However if we wanted to later process the same collection again, we'll have to copy save it:

var storedItems = new List<Item>();
foreach(var item in items){
    Process(item);
    storedItems .Add(item);
}

// Later
foreach(var item in storedItems){
    ProcessMore(item);
}



因为如果我们只希望做的foreach(。 ..中的项目)然后 temsSource.RetrieveItems()将再次调用。

我们可以使用 .ToList()右键前期,但是这将迫使我们等待的最后一个项目被检索之后,才可以开始处理第一个。

We could use .ToList() right upfront, but that would force us wait for the last item to be retrieved before we could start processing the first one.

:是否有一个的IEnumerable 的实施将遍历第一次像普通的LINQ查询结果,但在过程中会兑现,使第二个的foreach 会遍历存储的值?

Question: Is there an IEnumerable implementation that would iterate first time like regular LINQ query result, but would materialize in process so that second foreach would iterate over stored values?

推荐答案

一个有趣的挑战,所以我必须提供自己的解决方案。所以实际上乐趣我的解决方案现在在第3版第2版是一个简化基于我从Servy的反馈做出的。后来我意识到,我的解决方案有巨大的缺陷。如果缓存枚举的第一枚举没有完成没有缓存将完成。许多LINQ的扩展名如首先仅枚举枚举的,足以把工作做好,我不得不更新3版本,以便与缓存这项工作。

A fun challenge so I have to provide my own solution. So fun in fact that my solution now is in version 3. Version 2 was a simplification I made based on feedback from Servy. I then realized that my solution had huge drawback. If the first enumeration of the cached enumerable didn't complete no caching would be done. Many LINQ extensions like First and Take will only enumerate enough of the enumerable to get the job done and I had to update to version 3 to make this work with caching.

现在的问题是关于不涉及并发访问的枚举的后续枚举。不过,我已决定把我的解决方案线程安全的。它增加了一些复杂性和一点开销,但应允许在所有情况下使用的解决方案。

The question is about subsequent enumerations of the enumerable which does not involve concurrent access. Nevertheless I have decided to make my solution thread safe. It adds some complexity and a bit of overhead but should allow the solution to be used in all scenarios.

public static class EnumerableExtensions {

  public static IEnumerable<T> Cached<T>(this IEnumerable<T> source) {
    if (source == null)
      throw new ArgumentNullException("source");
    return new CachedEnumerable<T>(source);
  }

}

class CachedEnumerable<T> : IEnumerable<T> {

  readonly Object gate = new Object();

  readonly IEnumerable<T> source;

  readonly List<T> cache = new List<T>();

  IEnumerator<T> enumerator;

  bool isCacheComplete;

  public CachedEnumerable(IEnumerable<T> source) {
    this.source = source;
  }

  public IEnumerator<T> GetEnumerator() {
    lock (this.gate) {
      if (this.isCacheComplete)
        return this.cache.GetEnumerator();
      if (this.enumerator == null)
        this.enumerator = source.GetEnumerator();
    }
    return GetCacheBuildingEnumerator();
  }

  public IEnumerator<T> GetCacheBuildingEnumerator() {
    var index = 0;
    T item;
    while (TryGetItem(index, out item)) {
      yield return item;
      index += 1;
    }
  }

  bool TryGetItem(Int32 index, out T item) {
    lock (this.gate) {
      if (!IsItemInCache(index)) {
        // The iteration may have completed while waiting for the lock.
        if (this.isCacheComplete) {
          item = default(T);
          return false;
        }
        if (!this.enumerator.MoveNext()) {
          item = default(T);
          this.isCacheComplete = true;
          this.enumerator.Dispose();
          return false;
        }
        this.cache.Add(this.enumerator.Current);
      }
      item = this.cache[index];
      return true;
    }
  }

  bool IsItemInCache(Int32 index) {
    return index < this.cache.Count;
  }

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

}



扩展使用这样的( 的IEnumerable< T> ):

var cachedSequence = sequence.Cached();

// Pulling 2 items from the sequence.
foreach (var item in cachedSequence.Take(2))
  // ...

// Pulling 2 items from the cache and the rest from the source.
foreach (var item in cachedSequence)
  // ...

// Pulling all items from the cache.
foreach (var item in cachedSequence)
  // ...

有是轻微的泄漏,如果只可枚举的一部分枚举(如 cachedSequence.Take(2).ToList()。所使用的了ToList枚举将被设置但底层源枚举不设置,这是因为第一个2项被缓存和源枚举保持活着应当用于后续项的请求进行。在这种情况下,源枚举是仅清理eligigble的垃圾收集(这将是同一时间的可能很大缓存)时。

There is slight leak if only part of the enumerable is enumerated (e.g. cachedSequence.Take(2).ToList(). The enumerator that is used by ToList will be disposed but the underlying source enumerator is not disposed. This is because the first 2 items are cached and the source enumerator is kept alive should requests for subsequent items be made. In that case the source enumerator is only cleaned up when eligigble for garbage Collection (which will be the same time as the possibly large cache).

这篇关于是否有一个IEnumerable实现,只有在它的迭代源(例如LINQ)一次的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆