본문 바로가기
C#

2. yield return 에 대해 (내부 동작)

by Oz Driver 2025. 3. 9.

이전 글에서 yield return 을 사용하면 함수 실행을 중간에 멈췄다가 다시 실행할 수 있다는 것을 배웠습니다. 그렇다면, C# 은 어떻게 이를 가능하게 할까요? 이번 글에서는 yield return 이 내부적으로 어떻게 동작하는지 알아보겠습니다.

 

yield return 이 포함된 함수는 컴파일 단계에서 변환된다

yield return 을 포함한 함수는 일반적인 함수처럼 작동하는 것이 아니라, 컴파일러에 의해 특별한 클래스로 변환됩니다. 즉, C# 컴파일러는 해당 함수를 IEnumerator 인터페이스를 구현하는 클래스로 변환하여 실행 흐름을 제어합니다. 예를 들어, 다음 코드를 보겠습니다:

IEnumerator MyCoroutine()
{
    Console.WriteLine("Step 1");
    yield return null;
    Console.WriteLine("Step 2");
    yield return null;
    Console.WriteLine("Step 3");
}

 

이 함수는 컴파일러에 의해 아래와 같이 변환됩니다.

class MyCoroutineState : IEnumerator
{
    private int state = 0;
    public object Current { get; private set; }
    public bool MoveNext()
    {
        switch (state)
        {
            case 0:
                Console.WriteLine("Step 1");
                Current = null;
                state = 1;
                return true;
            case 1:
                Console.WriteLine("Step 2");
                Current = null;
                state = 2;
                return true;
            case 2:
                Console.WriteLine("Step 3");
                state = -1;
                return false;
        }
        return false;
    }
    
    // Reset() 함수는 거의 사용되지 않음. 
    public void Reset() { state = 0; }
}

 

컴파일러는 yield return 을 만나면 현재 상태를 저장하고 (state 변수), 다음 실행될 위치를 기록하여 중단할 수 있도록 변환합니다. 그리고 MoveNext() 가 호출되면 저장된 위치에서 다시 실행을 시작합니다.

yield return 을 만날 때마다, 자동으로 case 에 대응하는 코드를 생성해줍니다. 즉, yield return이 100개 있다면, 컴파일러는 각각의 위치에 대응하는 case 문을 100개 생성합니다. 이 구조는 내부에서 상태를 switch로 분기하며 실행 흐름을 나누는 방식입니다.

 

MoveNext() 는 어떻게 실행 흐름을 제어할까?

위 코드에서 MoveNext() 메서드는 switch 문을 사용하여 현재 실행 위치를 추적합니다.

최초 state 값이 0이면, Step 1을 출력하고 return true 를 합니다. 즉 함수를 빠져나갑니다. 

다음 번에 MoveNext() 가 호출되면 state 1이므로, Step 2를 출력하고 다시 함수를 빠져나갑니다.

마지막 Step 3을 출력한 후 state = -1로 설정되어 종료됩니다. 즉, MoveNext() 가 호출될 때마다 state 값을 찾아서 실행합니다. 

코드를 작성한 입장에서 보면, yield return 을 만날 때마다 다음 상태를 저장하고 함수를 빠져 나가게 되는 것입니다.

 

결국 MoveNext()는 yield return 을 기준으로 실행 흐름을 쪼개고, 그 지점을 기억했다가 한 번에 하나씩만 실행되도록 컨트롤하는 핵심 함수입니다.

 

마무리

yield return이 포함된 함수는 일반적인 함수가 아니라, 컴파일러에 의해 IEnumerator를 구현하는 클래스로 변환됩니다. 변환된 클래스는 MoveNext() 메서드를 사용하여 실행 위치를 관리하고, yield return 을 만나면 멈췄다가 다시 실행될 수 있습니다. 멈춘다는 표현을 보다 정확하게는 return true; 로 MoveNext() 함수를 빠져나가는 것입니다.

IEnumeratorMoveNext()를 호출하면서 실행 흐름을 제어하는 핵심 인터페이스입니다.

 

이번 글에서는 yield return이 내부적으로 어떻게 동작하는지 살펴보았습니다. 다음 글에서는 yield return이 코루틴과 어떻게 연결되는지 더 자세히 알아보겠습니다.