缓冲LINQ查询 [英] Buffering a LINQ query
问题描述
最后修改
我选择的蒂莫西的的答案,但如果你想要一个可爱的实现,它利用C#收益率语句检查的埃蒙的的答案: http://stackoverflow.com/a/19825659/145757
在默认情况下的 LINQ 查询是懒洋洋地流
的ToArray
/ 了ToList
提供的全缓冲,但首先他们的渴望其次它可能需要相当长的一段时间来完成与无限序列。
有什么办法让这两个行为的结合:流和缓冲作为生成飞价值观,他们,以便下次查询将不会触发的产生是已经被查询的元素
下面是一个基本的用例:
静态的IEnumerable< INT>号码
{
得到
{
INT I = -1;
而(真)
{
Console.WriteLine(生成{0},我+ 1);
收益回报++我;
}
}
}
静态无效的主要(字串[] args)
{
IEnumerable的< INT> evenNumbers = Numbers.Where(ⅰ= I标记%2 == 0);
的foreach(INT N的evenNumbers)
{
Console.WriteLine(读{0},N);
如果(N == 10)打破;
}
Console.WriteLine(==========);
的foreach(INT N的evenNumbers)
{
Console.WriteLine(读{0},N);
如果(N == 10)打破;
}
}
下面是输出:
生成0。
读0。
生成1。
生成2。
阅读2。
生成3。
生成4。
阅读4。
生成5。
生成6。
读6。
生成7。
生成8。
阅读8。
生成9。
生成10。
阅读10。
==========
生成0。
读0。
生成1。
生成2。
阅读2。
生成3。
生成4。
阅读4。
生成5。
生成6。
读6。
生成7。
生成8。
阅读8。
生成9。
生成10。
阅读10。
的产生code被触发22次。
我想它触发了11次,第一次枚举迭代。
然后在第二次迭代将受益于已生成的值。
这将是这样的:
的IEnumerable< INT> evenNumbers = Numbers.Where(ⅰ= I标记%2 == 0).Buffer();
对于那些熟悉的接收这是类似于行为 ReplaySubject
。
的IEnumerable< T> .Buffer()
扩展方法
公共静态EnumerableExtensions
{
公共静态BufferEnumerable< T>缓冲区(这IEnumerable的< T>源)
{
返回新BufferEnumerable< T>(来源);
}
}
公共类BufferEnumerable< T> :IEnumerable的< T&GT ;,了IDisposable
{
IEnumerator的< T>资源;
名单< T>缓冲;
公共BufferEnumerable(IEnumerable的< T>源)
{
this.source = source.GetEnumerator();
this.buffer =新的名单,其中,T>();
}
公众的IEnumerator< T>的GetEnumerator()
{
返回新BufferEnumerator< T>(源,缓冲区);
}
公共无效的Dispose()
{
source.Dispose()
}
}
公共类BufferEnumerator< T> :IEnumerator的< T>
{
IEnumerator的< T>资源;
名单< T>缓冲;
INT I = -1;
公共BufferEnumerator(IEnumerator的< T>源,列表< T>缓冲区)
{
this.source =来源;
this.buffer =缓冲区;
}
大众T电流
{
{返回缓冲区[I] }
}
公共BOOL的MoveNext()
{
我++;
如果(ⅰ&其中; buffer.Count)
返回true;
如果(!source.MoveNext())
返回false;
buffer.Add(source.Current);
返回true;
}
公共无效复位()
{
I = -1;
}
公共无效的Dispose()
{
}
}
用法
使用(VAR evenNumbers = Numbers.Where(I = I标记%2 == 0).Buffer())
{
...
}
建议
这里的关键点是,的IEnumerable< T>源
作为输入到缓存
方法不仅具有的GetEnumerator
调用一次,不管如何很多次缓存
的结果列举。所有调查员对缓存
的结果具有相同源枚举和内部列表。
FINAL EDIT:
I've chosen Timothy's answer but if you want a cuter implementation that leverages the C# yield statement check Eamon's answer: http://stackoverflow.com/a/19825659/145757
By default LINQ queries are lazily streamed.
ToArray
/ToList
give full buffering but first they're eager and secondly it may take quite some time to complete with an infinite sequence.
Is there any way to have a combination of both behaviors : streaming and buffering values on the fly as they are generated, so that the next querying won't trigger the generation of the elements that have already been queried.
Here is a basic use-case:
static IEnumerable<int> Numbers
{
get
{
int i = -1;
while (true)
{
Console.WriteLine("Generating {0}.", i + 1);
yield return ++i;
}
}
}
static void Main(string[] args)
{
IEnumerable<int> evenNumbers = Numbers.Where(i => i % 2 == 0);
foreach (int n in evenNumbers)
{
Console.WriteLine("Reading {0}.", n);
if (n == 10) break;
}
Console.WriteLine("==========");
foreach (int n in evenNumbers)
{
Console.WriteLine("Reading {0}.", n);
if (n == 10) break;
}
}
Here is the output:
Generating 0.
Reading 0.
Generating 1.
Generating 2.
Reading 2.
Generating 3.
Generating 4.
Reading 4.
Generating 5.
Generating 6.
Reading 6.
Generating 7.
Generating 8.
Reading 8.
Generating 9.
Generating 10.
Reading 10.
==========
Generating 0.
Reading 0.
Generating 1.
Generating 2.
Reading 2.
Generating 3.
Generating 4.
Reading 4.
Generating 5.
Generating 6.
Reading 6.
Generating 7.
Generating 8.
Reading 8.
Generating 9.
Generating 10.
Reading 10.
The generation code is triggered 22 times.
I'd like it to be triggered 11 times, the first time the enumerable is iterated.
Then the second iteration would benefit from the already generated values.
It would be something like:
IEnumerable<int> evenNumbers = Numbers.Where(i => i % 2 == 0).Buffer();
For those familiar with Rx it's a behavior similar to a ReplaySubject
.
IEnumerable<T>.Buffer()
extension method
public static EnumerableExtensions
{
public static BufferEnumerable<T> Buffer(this IEnumerable<T> source)
{
return new BufferEnumerable<T>(source);
}
}
public class BufferEnumerable<T> : IEnumerable<T>, IDisposable
{
IEnumerator<T> source;
List<T> buffer;
public BufferEnumerable(IEnumerable<T> source)
{
this.source = source.GetEnumerator();
this.buffer = new List<T>();
}
public IEnumerator<T> GetEnumerator()
{
return new BufferEnumerator<T>(source, buffer);
}
public void Dispose()
{
source.Dispose()
}
}
public class BufferEnumerator<T> : IEnumerator<T>
{
IEnumerator<T> source;
List<T> buffer;
int i = -1;
public BufferEnumerator(IEnumerator<T> source, List<T> buffer)
{
this.source = source;
this.buffer = buffer;
}
public T Current
{
get { return buffer[i]; }
}
public bool MoveNext()
{
i++;
if (i < buffer.Count)
return true;
if (!source.MoveNext())
return false;
buffer.Add(source.Current);
return true;
}
public void Reset()
{
i = -1;
}
public void Dispose()
{
}
}
Usage
using (var evenNumbers = Numbers.Where(i => i % 2 == 0).Buffer())
{
...
}
Comments
The key point here is that the IEnumerable<T> source
given as input to the Buffer
method only has GetEnumerator
called once, regardless of how many times the result of Buffer
is enumerated. All enumerators for the result of Buffer
share the same source enumerator and internal list.
这篇关于缓冲LINQ查询的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!