다음을 통해 공유


반복기(C#)

반복기를 사용하여 목록 및 배열과 같은 컬렉션을 단계별로 실행할 수 있습니다.

반복기 메서드 또는 get 접근자는 컬렉션에 대해 사용자 지정 반복을 수행합니다. 반복기 메서드는 yield return 문을 사용하여 각 요소를 한 번에 하나씩 반환합니다. 명령문 yield return 에 도달하면 코드의 현재 위치가 저장됩니다. 다음에 반복기 함수가 호출될 때 해당 위치에서 실행이 다시 시작됩니다.

클라이언트 코드에서 foreach 문 또는 LINQ 쿼리를 사용하여 반복기를 소비합니다.

다음 예제에서 foreach 루프의 첫 번째 반복은 SomeNumbers 문에 도달할 때까지 yield return 반복기 메서드에서 실행을 진행시킵니다. 이 반복은 값 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 반복을 종료할 수 있습니다.

비고

Simple Iterator 예제를 제외한 이 항목의 모든 예제에는 System.Collections 네임스페이스에 대한 System.Collections.Generic 지시문을 포함합니다.

단순 반복기

다음 예제에는 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 .

클래스 인스턴스(foreach)를 참조하는 문은 theZoo 메서드를 암시적으로 호출합니다GetEnumerator. foreach 문은 BirdsMammals 속성을 참조하며 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 .

제네릭 메서드 외에도 제네릭 GetEnumeratorGetEnumerator 이 아닌 메서드도 구현해야 합니다. 이렇게 된 이유는 IEnumerable<T>이/가 IEnumerable로부터 상속받기 때문입니다. 비제네릭 구현은 제네릭 구현에 의존합니다.

이 예제에서는 명명된 반복기를 사용하여 동일한 데이터 컬렉션을 반복하는 다양한 방법을 지원합니다. 이러한 명명된 반복기는 TopToBottomBottomToTop 속성, 그리고 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 접근자로 사용될 수 있습니다. 이벤트, 인스턴스 생성자, 정적 생성자 또는 정적 종료자에서는 반복기가 발생할 수 없습니다.

암시적 변환은 yield return 문에 있는 식 형식에서 반복기가 IEnumerable<T>으로 반환하는 형식 인수로 존재해야 합니다.

C#에서는 반복기 메서드에 in, ref 또는 out 매개 변수가 있을 수 없습니다.

C#에서는 yield가 자체로는 예약어가 아니며, return 또는 break 키워드 앞에 사용될 때만 특별한 의미를 가집니다.

기술 구현

반복기를 메서드로 작성하지만 컴파일러는 해당 반복기를 중첩된 클래스( 사실상 상태 컴퓨터)로 변환합니다. 이 클래스는 클라이언트 코드의 루프가 계속되는 동안 foreach 반복기의 위치를 추적합니다.

컴파일러가 수행하는 작업을 확인하려면 Ildasm.exe 도구를 사용하여 반복기 메서드에 대해 생성된 공통 중간 언어 코드를 볼 수 있습니다.

클래스 또는 구조체에 대한 반복기를 만들 때 전체 IEnumerator 인터페이스를 구현할 필요가 없습니다. 컴파일러가 반복기를 감지하면 Current 또는 MoveNext 인터페이스의 Dispose, IEnumerator, 및 IEnumerator<T> 메서드를 자동으로 생성합니다.

루프의 foreach 연속된 반복(또는 직접 호출 IEnumerator.MoveNext)에서 다음 반복기 코드 본문은 이전 yield return 문 다음에 다시 시작됩니다. 그런 다음 다음 yield return 문으로 계속되며, 반복기 본문의 끝에 도달하거나 yield break 문이 발견될 때까지 진행됩니다.

메서드 IEnumerator.Reset을(를) 반복기는 지원하지 않습니다. 처음부터 반복하려면 새 반복기를 가져와야 합니다. 반복기 메서드에서 반환된 반복기에 대해 Reset을 호출하면 NotSupportedException이(가) 발생합니다.

자세한 내용은 C# 언어 사양을 참조하세요.

반복기 사용

반복기를 사용하면 복잡한 코드를 사용하여 목록 시퀀스를 채워야 하는 경우 루프의 foreach 단순성을 유지할 수 있습니다. 이 작업은 다음을 수행하려는 경우에 유용할 수 있습니다.

  • 첫 번째 foreach 루프 반복 후 목록 시퀀스를 수정합니다.

  • foreach 루프의 첫 번째 반복 이전에 큰 목록을 완전히 로드하지 마세요. 예를 들어 테이블 행을 일괄적으로 불러오는 페이지를 나누어 가져오는 작업이 있습니다. 또 다른 예로 EnumerateFiles .NET에서 반복기를 구현하는 메서드가 있습니다.

  • 반복기에서 목록 작성을 캡슐화합니다. 반복기 메서드에서 목록을 빌드한 다음 각 결과를 루프로 생성할 수 있습니다.

참고하십시오