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>
IteratorBlock
foreach
foreach (var evenNumber in evenNumbers)
Console.WriteLine(eventNumber);
열거 가능 항목을 두 번 열거하면 매번 상태 머신의 새 인스턴스가 작성되고 반복자 블록은 동일한 코드를 두 번 실행합니다.
LINQ 방법이 좋아 공지 것으로 ToList()
, ToArray()
, First()
, Count()
등이 사용하는 foreach
열거 가능한을 열거 루프를. 예를 들어 ToList()
열거 가능한 모든 요소를 열거하고 목록에 저장합니다. 반복자 블록을 다시 실행하지 않고도 열거 가능한 모든 요소를 얻기 위해 목록에 액세스 할 수 있습니다. CPU를 사용하여 열거 가능한 요소를 여러 번 생성하는 것과 메모리 요소와 같은 메소드를 사용할 때 열거 할 요소를 여러 번 액세스하는 메모리 사이에는 상충 관계가 있습니다 ToList()
.