반복기(C#)
반복기는 목록 및 배열과 같은 컬렉션을 단계별로 실행하는 데 사용할 수 있습니다.
반복기 메서드 또는 get
접근자는 컬렉션에 대해 사용자 지정 반복을 수행합니다. 반복기 메서드는 yield return 문을 사용하여 각 요소를 한 번에 하나씩 반환합니다. yield return
문에 도달하면 코드의 현재 위치가 기억됩니다. 다음에 반복기 함수가 호출되면 해당 위치에서 실행이 다시 시작됩니다.
클라이언트 코드에서 foreach 문 또는 LINQ 쿼리를 사용하여 반복기를 이용합니다.
다음 예제에서 foreach
루프의 첫 번째 반복은 첫 번째 yield return
문에 도달할 때까지 SomeNumbers
반복기 메서드에서 실행이 계속되도록 합니다. 이 반복은 3 값을 반환하며 반복기 메서드에서 현재 위치는 유지됩니다. 루프의 다음 반복에서는 반복기 메서드의 실행이 중지되었던 위치에서 계속되고 yield return
문에 도달하면 다시 중지됩니다. 이 반복은 값 5를 반환하며 반복기 메서드에서 현재 위치는 다시 유지됩니다. 루프는 반복기 메서드의 끝에 도달하면 완료됩니다.
static void Main()
{
foreach (int number in SomeNumbers())
{
Console.Write(number.ToString() + " ");
}
// Output: 3 5 8
Console.ReadKey();
}
public static System.Collections.IEnumerable SomeNumbers()
{
yield return 3;
yield return 5;
yield return 8;
}
반복기 메서드 또는 get
접근자의 반환 형식은 IEnumerable, IEnumerable<T>, IEnumerator 또는 IEnumerator<T>일 수 있습니다.
yield break
문을 사용하여 반복기를 종료할 수 있습니다.
참고 항목
단순 반복기 예제를 제외한 이 항목의 모든 예제에 대해 System.Collections
및 System.Collections.Generic
네임스페이스에 대한 using 지시문을 포함하세요.
단순 반복기
다음 예제에는 for 루프 내에 단일 yield return
문이 있습니다. Main
에서 foreach
문 본문을 반복할 때마다 다음 yield return
문으로 진행하는 반복기 함수에 대한 호출이 생성됩니다.
static void Main()
{
foreach (int number in EvenSequence(5, 18))
{
Console.Write(number.ToString() + " ");
}
// Output: 6 8 10 12 14 16 18
Console.ReadKey();
}
public static System.Collections.Generic.IEnumerable<int>
EvenSequence(int firstNumber, int lastNumber)
{
// Yield even numbers in the range.
for (int number = firstNumber; number <= lastNumber; number++)
{
if (number % 2 == 0)
{
yield return number;
}
}
}
컬렉션 클래스 만들기
다음 예제에서 DaysOfTheWeek
클래스는 IEnumerable 인터페이스를 구현하며, GetEnumerator 메서드가 필요합니다. 컴파일러는 GetEnumerator
메서드를 암시적으로 호출하며, IEnumerator가 반환됩니다.
GetEnumerator
메서드는 yield return
문을 사용하여 각 문자열을 한 번에 하나씩 반환합니다.
static void Main()
{
DaysOfTheWeek days = new DaysOfTheWeek();
foreach (string day in days)
{
Console.Write(day + " ");
}
// Output: Sun Mon Tue Wed Thu Fri Sat
Console.ReadKey();
}
public class DaysOfTheWeek : IEnumerable
{
private string[] days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
public IEnumerator GetEnumerator()
{
for (int index = 0; index < days.Length; index++)
{
// Yield each day of the week.
yield return days[index];
}
}
}
다음 예제에서는 동물 컬렉션을 포함하는 Zoo
클래스를 만듭니다.
클래스 인스턴스(theZoo
)를 참조하는 foreach
문은 GetEnumerator
메서드를 암시적으로 호출합니다. Birds
및 Mammals
속성을 참조하는 foreach
문은 AnimalsForType
명명된 반복기 메서드를 사용합니다.
static void Main()
{
Zoo theZoo = new Zoo();
theZoo.AddMammal("Whale");
theZoo.AddMammal("Rhinoceros");
theZoo.AddBird("Penguin");
theZoo.AddBird("Warbler");
foreach (string name in theZoo)
{
Console.Write(name + " ");
}
Console.WriteLine();
// Output: Whale Rhinoceros Penguin Warbler
foreach (string name in theZoo.Birds)
{
Console.Write(name + " ");
}
Console.WriteLine();
// Output: Penguin Warbler
foreach (string name in theZoo.Mammals)
{
Console.Write(name + " ");
}
Console.WriteLine();
// Output: Whale Rhinoceros
Console.ReadKey();
}
public class Zoo : IEnumerable
{
// Private members.
private List<Animal> animals = new List<Animal>();
// Public methods.
public void AddMammal(string name)
{
animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal });
}
public void AddBird(string name)
{
animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Bird });
}
public IEnumerator GetEnumerator()
{
foreach (Animal theAnimal in animals)
{
yield return theAnimal.Name;
}
}
// Public members.
public IEnumerable Mammals
{
get { return AnimalsForType(Animal.TypeEnum.Mammal); }
}
public IEnumerable Birds
{
get { return AnimalsForType(Animal.TypeEnum.Bird); }
}
// Private methods.
private IEnumerable AnimalsForType(Animal.TypeEnum type)
{
foreach (Animal theAnimal in animals)
{
if (theAnimal.Type == type)
{
yield return theAnimal.Name;
}
}
}
// Private class.
private class Animal
{
public enum TypeEnum { Bird, Mammal }
public string Name { get; set; }
public TypeEnum Type { get; set; }
}
}
제네릭 목록과 함께 반복기 사용
다음 예제에서 Stack<T> 제네릭 클래스는 IEnumerable<T> 제네릭 인터페이스를 구현합니다. Push 메서드는 T
형식의 배열에 값을 할당합니다. GetEnumerator 메서드는 yield return
문을 사용하여 배열 값을 반환합니다.
제네릭 GetEnumerator 메서드뿐 아니라 제네릭이 아닌 GetEnumerator 메서드도 구현해야 합니다. IEnumerable<T>이 IEnumerable에서 상속하기 때문입니다. 제네릭이 아닌 구현은 제네릭 구현을 따릅니다.
예제에서는 명명된 반복기를 사용하여 동일한 데이터 컬렉션을 반복하는 다양한 방법을 지원합니다. 이러한 명명된 반복기는 TopToBottom
및 BottomToTop
속성과 TopN
메서드입니다.
BottomToTop
속성은 get
접근자에서 반복기를 사용합니다.
static void Main()
{
Stack<int> theStack = new Stack<int>();
// Add items to the stack.
for (int number = 0; number <= 9; number++)
{
theStack.Push(number);
}
// Retrieve items from the stack.
// foreach is allowed because theStack implements IEnumerable<int>.
foreach (int number in theStack)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3 2 1 0
// foreach is allowed, because theStack.TopToBottom returns IEnumerable(Of Integer).
foreach (int number in theStack.TopToBottom)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3 2 1 0
foreach (int number in theStack.BottomToTop)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 0 1 2 3 4 5 6 7 8 9
foreach (int number in theStack.TopN(7))
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3
Console.ReadKey();
}
public class Stack<T> : IEnumerable<T>
{
private T[] values = new T[100];
private int top = 0;
public void Push(T t)
{
values[top] = t;
top++;
}
public T Pop()
{
top--;
return values[top];
}
// This method implements the GetEnumerator method. It allows
// an instance of the class to be used in a foreach statement.
public IEnumerator<T> GetEnumerator()
{
for (int index = top - 1; index >= 0; index--)
{
yield return values[index];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerable<T> TopToBottom
{
get { return this; }
}
public IEnumerable<T> BottomToTop
{
get
{
for (int index = 0; index <= top - 1; index++)
{
yield return values[index];
}
}
}
public IEnumerable<T> TopN(int itemsFromTop)
{
// Return less than itemsFromTop if necessary.
int startIndex = itemsFromTop >= top ? 0 : top - itemsFromTop;
for (int index = top - 1; index >= startIndex; index--)
{
yield return values[index];
}
}
}
구문 정보
반복기는 메서드 또는 get
접근자로 발생할 수 있습니다. 이벤트, 인스턴스 생성자, 정적 생성자 또는 정적 종료자에서는 반복기가 발생할 수 없습니다.
반복기에서 반환된 IEnumerable<T>
의 형식 인수에 대한 yield return
문의 식 형식에는 암시적 변환이 있어야 합니다.
C#에서 반복기 메서드는 in
, ref
또는 out
매개 변수를 사용할 수 없습니다.
C#에서 yield
는 예약어가 아니며 return
또는 break
키워드 앞에서 사용되는 경우에만 특별한 의미가 있습니다.
기술 구현
반복기를 메서드로 작성하는 경우에도 컴파일러는 실제로 상태 시스템인 중첩 클래스로 변환합니다. 이 클래스는 클라이언트 코드의 foreach
루프가 계속되는 한 반복기의 위치를 추적합니다.
컴파일러의 용도를 확인하려면 Ildasm.exe 도구를 사용하여 반복기 메서드에 대해 생성되는 공용 중간 언어 코드를 확인할 수 있습니다.
class 또는 struct에 대해 반복기를 만드는 경우 전체 IEnumerator 인터페이스를 구현할 필요가 없습니다. 컴파일러는 반복기를 검색할 경우 IEnumerator 또는 IEnumerator<T> 인터페이스의 Current
, MoveNext
및 Dispose
메서드를 자동으로 생성합니다.
foreach
루프를 연속 반복하거나 IEnumerator.MoveNext
를 직접 호출하면 다음 반복기 코드 본문이 이전 yield return
문 다음에 다시 시작됩니다. 그런 후 반복기 본문의 끝에 도달하거나 yield break
문이 나타날 때까지 다음 yield return
문을 계속 실행합니다.
반복기는 IEnumerator.Reset 메서드를 지원하지 않습니다. 처음부터 다시 반복하려면 새 반복기를 가져와야 합니다. 반복기 메서드에 의해 반환된 반복기에서 Reset를 호출하면 NotSupportedException가 throw됩니다.
자세한 내용은 C# 언어 사양을 참조하세요.
반복기 사용
반복기를 사용하면 복잡한 코드를 사용하여 목록 시퀀스를 채워야 하는 경우 foreach
루프의 단순성을 유지할 수 있습니다. 이 기능은 다음을 수행하려는 경우에 유용할 수 있습니다.
첫 번째
foreach
루프 반복 후 목록 시퀀스를 수정합니다.첫 번째
foreach
루프 반복 전에 큰 목록이 완전히 로드되지 않도록 합니다. 한 가지 예로 테이블 행을 일괄 로드하는 페이징 페치가 있으며, 또 다른 예로 .NET에서 반복기를 구현하는 EnumerateFiles 메서드가 있습니다.반복기에서 목록 작성을 캡슐화합니다. 반복기 메서드에서 목록을 빌드한 후 루프에서 각 결과를 생성할 수 있습니다.
참고 항목
.NET