yield키워드는 당신이를 만들 수 있습니다 IEnumerable<T>온 형태로 반복자 블록 . 이 반복자 블록은 지연된 실행을 지원 하며 개념에 익숙하지 않은 경우 거의 마술처럼 보일 수 있습니다. 그러나 하루가 끝나면 이상한 트릭없이 실행되는 코드 일뿐입니다.
반복자 블록은 구문 설탕으로 설명 할 수 있는데, 여기서 컴파일러는 열거 자의 열거가 얼마나 진행되었는지 추적하는 상태 머신을 생성합니다. 열거 형을 열거하기 위해 종종 foreach루프를 사용합니다 . 그러나 foreach루프는 또한 구문 설탕입니다. 따라서 실제 코드에서 제거 된 두 개의 추상화이므로 처음에 모든 코드가 어떻게 작동하는지 이해하기 어려울 수 있습니다.
매우 간단한 반복자 블록이 있다고 가정하십시오.
IEnumerable<int> IteratorBlock()
{
Console.WriteLine("Begin");
yield return 1;
Console.WriteLine("After 1");
yield return 2;
Console.WriteLine("After 2");
yield return 42;
Console.WriteLine("End");
}
실제 반복자 블록에는 종종 조건과 루프가 있지만 조건을 확인하고 루프를 풀면 여전히 yield다른 코드와 인터리브 된 명령문으로 종료됩니다 .
반복자 블록을 열거하기 위해 foreach루프가 사용됩니다.
foreach (var i in IteratorBlock())
Console.WriteLine(i);
출력은 다음과 같습니다 (여기서는 놀라지 않습니다).
시작
1
1 후
2
2 이후
42
종료
위에서 언급 한 바와 같이 foreach구문 설탕은 다음과 같습니다.
IEnumerator<int> enumerator = null;
try
{
enumerator = IteratorBlock().GetEnumerator();
while (enumerator.MoveNext())
{
var i = enumerator.Current;
Console.WriteLine(i);
}
}
finally
{
enumerator?.Dispose();
}
이것을 풀기 위해 추상화를 제거한 시퀀스 다이어그램을 작성했습니다.

컴파일러에 의해 생성 된 상태 머신은 열거자를 구현하지만 다이어그램을보다 명확하게하기 위해 별도의 인스턴스로 표시했습니다. (상태 머신이 다른 스레드에서 열거 될 때 실제로 별도의 인스턴스를 얻지 만 여기서는 그 세부 사항이 중요하지 않습니다.)
반복자 블록을 호출 할 때마다 상태 머신의 새 인스턴스가 작성됩니다. 그러나 반복자 블록의 코드 enumerator.MoveNext()는 처음 실행될 때까지 실행 되지 않습니다 . 이것이 지연된 실행 작동 방식입니다. 다음은 (어리석지 않은) 예입니다.
var evenNumbers = IteratorBlock().Where(i => i%2 == 0);
이 시점에서 반복자는 실행되지 않았습니다. 이 Where절은 반환 된 IEnumerable<T>줄 바꿈을 새로 작성 하지만이 열거 가능 항목은 아직 열거되지 않았습니다. 루프 를 실행할 때 발생 합니다.IEnumerable<T>IteratorBlockforeach
foreach (var evenNumber in evenNumbers)
Console.WriteLine(eventNumber);
열거 가능 항목을 두 번 열거하면 매번 상태 머신의 새 인스턴스가 작성되고 반복자 블록은 동일한 코드를 두 번 실행합니다.
LINQ 방법이 좋아 공지 것으로 ToList(), ToArray(), First(), Count()등이 사용하는 foreach열거 가능한을 열거 루프를. 예를 들어 ToList()열거 가능한 모든 요소를 열거하고 목록에 저장합니다. 반복자 블록을 다시 실행하지 않고도 열거 가능한 모든 요소를 얻기 위해 목록에 액세스 할 수 있습니다. CPU를 사용하여 열거 가능한 요소를 여러 번 생성하는 것과 메모리 요소와 같은 메소드를 사용할 때 열거 할 요소를 여러 번 액세스하는 메모리 사이에는 상충 관계가 있습니다 ToList().