최종 편집:
나는've 선택티's 응답만을 원하는 경우 귀엽 구현을 활용하는 C#수익성명 확인Eamon's 답변:https://stackoverflow.com/a/19825659/145757
기본적으로LINQ쿼리느리게 스트리밍.
ToArray
/ToList
주전체 buffering지만 그들은 처음're망며 둘째로 그것은 꽤 많은 시간이 걸릴 수 있습으로 완료하기 위해 무한한다.
방법은 없을 조합의를 모두 동작:스트리밍과완충값 즉석에서 그들이 생성되는,그래서는 다음을 쿼리하지't 거 세대의 요소는 이미 조회됩니다.
여기에는 기본적인 사용 경우:
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;
}
}
출력은 다음과 같습니다.
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.
생성 코드가 트리거되 22times.
나는'd 같은 그것을 트리거 될 11 회,처음 열거가 반복되는.
다음 두 번째로 반복해 주시면 도움이 될 것입으로 생성되는 값입니다.
그것은 것:
IEnumerable<int> evenNumbers = Numbers.Where(i => i % 2 == 0).Buffer();
에 익숙한 사람들을 위해Rxit's 는 동작과 비슷한ReplaySubject
.
페이<T>.버퍼()
확장자 방법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()
{
}
}
using (var evenNumbers = Numbers.Where(i => i % 2 == 0).Buffer())
{
...
}
여기에서 중요한 점은페이<T>원
어 입력으로버퍼
방법만GetEnumerator
이라는 한 번에 관계없이 여러 번 결과의Buffer
열거됩니다. 모두 열거자는 결과에 대한 의버퍼
공유하는 동일한 원본 열거자와 내부의 목록입니다.
사용할 수 있는Microsoft.FSharp.컬렉션이 있습니다.지금 기록<>
형식에서는 F#파워 팩(그래는 C#없 F#설치해도 문제없어요!) 니다. It's Nuget 패키지FSPowerPack.핵심입니다.커뮤니티
.
특히,당신은 당신이 전화를 원하는LazyListModule.ofSeq(...)
반환하는지금 기록<T>
를 구현하는페이<T>
고 게으른 캐시됩니다.
귀하의 경우에는,사용법의 문제입니다...
var evenNumbers = LazyListModule.ofSeq(Numbers.Where(i => i % 2 == 0));
var cachedEvenNumbers = LazyListModule.ofSeq(evenNumbers);
하지만 나는 개인적으로 선호하 var 이러한 모든 경우에,이 의미는 컴파일 시간 형식이 있다페이<>
-지 않을 가능성이 있을 수 있습니다. 의 또 다른 장점은 F#non-인터페이스 유형은 그들이 노출에는 효율적인 작업할 수 있't do efficienly 일반 IEnumerables 등LazyListModule.skip
.
나는'm 지 여부를 확인지금 기록
은 스레드에 안전하지만,저는 그것을 의심입니다.
또 다른 대안에서 지적된 코멘트는 아래에(있을 경우 F#설치)는SeqModule.캐시
(네임스페이스Microsoft.FSharp.컬렉션
,it'될 것에 GACed 어셈블리 FSharp.Core.dll 도)동일한 효과적인 동작입니다. 다음과 같다.NET enumerables,Seq.캐시
지 않't 꼬리를 가지고 있(또는 건너뛰기)연산자를 사용할 수 있는 효율적으로 체인입니다.
Thread-safe:다른 솔루션과는 달리 이 질문Seq.캐시은 스레드에 안전 감각을 가질 수 있는 여러자 병렬로 실행(각 열거되지 않은 스레드에 안전하).
****성능내가 빠른 벤치마크하고,지금 기록
열거는 최소 4 배 이상 오버헤드를 보다SeqModule.캐시
변형은 적어도 세 가지 배 이상 오버헤드보다는 사용자 정의 구현의 답변이 있습니다. 는 동안 그래서,F#개 작업,그들이'다시 매우 빠르다. Note3-12 시간 느린 여전히지 않는't 에 비해 매우 느리게 열거는 않습니다(말)I/O 거나 어떤 아닌 사소한 계산,그래서 이 아't 문제는 대부분의 시간이지만 그's 좋에 각별히 주의 하시기 바랍니다.
TL;DR해야 하는 경우 효율적이,thread-safe 캐시 열거할 사용,SeqModule.캐시
.
에 건물[에이먼's 응답 위](https://stackoverflow.com/a/19825659/562906 다)여기에서's 는 또 다른 기능적인 솔루션을(아 새로운 유형)는 작품으로도 동시에 평가입니다. 이것을 보여 주는 일반적인 패턴(반복 공용 상태)에 기초가 되는 이 문제를 해결합니다.
먼저 우리는 정의 매우 일반적으로 도우미는 방법을 의미할 수 있도록하는 시뮬레이션 기능은 중의익명 반복기에 C#:
public static IEnumerable<T> Generate<T>(Func<Func<Tuple<T>>> generator)
{
var tryGetNext = generator();
while (true)
{
var result = tryGetNext();
if (null == result)
{
yield break;
}
yield return result.Item1;
}
}
를 생성하는 같은 프로그램으로 상태가 됩니다. 그것은지를 반환하는 함수 초기 상태,그리고 생성하는 함수는 익명으로수확량에 반환에서
는 경우,그것이 허용되었다 C#으로 작성됩니다. 상태를 반환한초기화
의 의미를 당 열거하는 동안 더 많은 글로벌 상태(간에 공유되는 모든 열거)에 의해 유지될 수 있습니다 호출자를 생성하는 등에서 폐쇄 변수로서 우리는'을 보여줄 것이다.
이제 우리가 사용할 수 있습니다 이에 대한"buffered 열거할 수"문제가:
public static IEnumerable<T> Cached<T>(IEnumerable<T> enumerable)
{
var cache = new List<T>();
var enumerator = enumerable.GetEnumerator();
return Generate<T>(() =>
{
int pos = -1;
return () => {
pos += 1;
if (pos < cache.Count())
{
return new Tuple<T>(cache[pos]);
}
if (enumerator.MoveNext())
{
cache.Add(enumerator.Current);
return new Tuple<T>(enumerator.Current);
}
return null;
};
});
}
이 대답을 결합한 간결하고 명확[sinelaw's[답변](https://stackoverflow.com/a/19825389/42921 다)및 지원을 위해 여러 개의 열거[디모데는's답변:
public static IEnumerable<T> Cached<T>(this IEnumerable<T> enumerable) {
return CachedImpl(enumerable.GetEnumerator(), new List<T>());
}
static IEnumerable<T> CachedImpl<T>(IEnumerator<T> source, List<T> buffer) {
int pos=0;
while(true) {
if(pos == buffer.Count)
if (source.MoveNext())
buffer.Add(source.Current);
else
yield break;
yield return buffer[pos++];
}
}
핵심 아이디어가 사용하면수익을 반환
구문들에 대한 짧은 열거 구현,하지만 당신은 여전히 필요한 상태계 여부를 결정을 얻을 수 있는 다음 요소로부터 버퍼 또는지 여부를 확인해야 할 기본거움을 누리고 있다고 말한다.
*제한사항:**이하지 않을 수 있 스레드에 안전하지 않으며,폐기본 열거자(는,일반적으로,매우 까다로운 일을 기본으로 캐시되지 않은 열거자를 유지해야 undisposed 만큼 모든 캐시 enumerabl 될 수 있습니다).
멀리로 나가 알고있는 방법은 기본적으로 제공되지 않을 이렇게 하는 이제 당신은 그것을 언급은 약간의 놀라(나의 추측은 주어진 주파수와 함께하는 한 원하는 것 이 옵션을 사용하려면,그것은 아마도 가치가 필요한 노력을 분석 코드를 확인하는 것 발전기를 제공합 정확한 순서 모든 시간).
할 수 있는 그러나 그것을 구현하 자신입니다. 쉬운 방법이 될 것이 전화에트로
var evenNumbers = Numbers.Where(i => i % 2 == 0).
var startOfList = evenNumbers.Take(10).ToList();
// use startOfList instead of evenNumbers in the loop.
더 일반적으로 그리고 정확하게,당신은 그것을 할 수 있습에서 발전기를목록<int>cache
모든 시간을 생성하는 새로운 숫자 추가캐시
기 전에 당신은`수익을 반환합니다. 그런 다음 경우 루프를 통해 다시,처음까지 제공하는 모든 캐시된 번호입니다. E.g.
List<int> cachedEvenNumbers = new List<int>();
IEnumerable<int> EvenNumbers
{
get
{
int i = -1;
foreach(int cached in cachedEvenNumbers)
{
i = cached;
yield return cached;
}
// Note: this while loop now starts from the last cached value
while (true)
{
Console.WriteLine("Generating {0}.", i + 1);
yield return ++i;
}
}
}
나는 당신이 생각하는 경우 이에 대한 충분히 당신이 가지고 올 수 있는 일반 구현을의페이<T>.버퍼()
확장 방법-다시 요구 사항은 열거하지 않't 간 변화 통화 및 문제는다면 그것은 가치가 있다.
여기's an불완전한 아직콤팩트'functional'구현(새로운 정의된 형식).
버그를 허용하지 않는다는 것 동시에 열거에 있습니다.
원래 설명:
첫 번째 기능이 있어야 되는 익명 lambda 내부 두 번째지만,C#허용하지 않는수익
에 익명 lambdas:
// put these in some extensions class
private static IEnumerable<T> EnumerateAndCache<T>(IEnumerator<T> enumerator, List<T> cache)
{
while (enumerator.MoveNext())
{
var current = enumerator.Current;
cache.Add(current);
yield return current;
}
}
public static IEnumerable<T> ToCachedEnumerable<T>(this IEnumerable<T> enumerable)
{
var enumerator = enumerable.GetEnumerator();
var cache = new List<T>();
return cache.Concat(EnumerateAndCache(enumerator, cache));
}
사용법:
var enumerable = Numbers.ToCachedEnumerable();
전체 신용에이먼 Nerbonnesinelaw에 대한 그들의 답변을,그냥 몇 가지 바뀌! First,release 열거자 할 때 그것은 완료됩니다. 둘째를 보호하는 기본 열거자 잠금 그렇게 열거 가능한 안전하게 사용될 수 있습에서 여러 스레드입니다.
// This is just the same as @sinelaw's Generator but I didn't like the name
public static IEnumerable<T> AnonymousIterator<T>(Func<Func<Tuple<T>>> generator)
{
var tryGetNext = generator();
while (true)
{
var result = tryGetNext();
if (null == result)
{
yield break;
}
yield return result.Item1;
}
}
// Cached/Buffered/Replay behaviour
public static IEnumerable<T> Buffer<T>(this IEnumerable<T> self)
{
// Rows are stored here when they've been fetched once
var cache = new List<T>();
// This counter is thread-safe in that it is incremented after the item has been added to the list,
// hence it will never give a false positive. It may give a false negative, but that falls through
// to the code which takes the lock so it's ok.
var count = 0;
// The enumerator is retained until it completes, then it is discarded.
var enumerator = self.GetEnumerator();
// This lock protects the enumerator only. The enumerable could be used on multiple threads
// and the enumerator would then be shared among them, but enumerators are inherently not
// thread-safe so a) we must protect that with a lock and b) we don't need to try and be
// thread-safe in our own enumerator
var lockObject = new object();
return AnonymousIterator<T>(() =>
{
int pos = -1;
return () =>
{
pos += 1;
if (pos < count)
{
return new Tuple<T>(cache[pos]);
}
// Only take the lock when we need to
lock (lockObject)
{
// The counter could have been updated between the check above and this one,
// so now we have the lock we must check again
if (pos < count)
{
return new Tuple<T>(cache[pos]);
}
// Enumerator is set to null when it has completed
if (enumerator != null)
{
if (enumerator.MoveNext())
{
cache.Add(enumerator.Current);
count += 1;
return new Tuple<T>(enumerator.Current);
}
else
{
enumerator = null;
}
}
}
}
return null;
};
});
}