본문 바로가기
C#

foreach() 가 내부적으로 동작하는 방식

by Oz Driver 2025. 7. 15.

C#에서 foreach() 문은 아주 간편하게 컬렉션을 순회할 수 있도록 도와주는 문법입니다. 하지만 이 문법 뒤에는 실제로 어떤 일이 일어나는지, 내부적으로 어떤 코드가 실행되는지를 이해하면 C#의 컬렉션 처리 방식에 대해 훨씬 깊은 통찰을 얻을 수 있습니다.

 

foreach 구문의 일반적인 형태

string[] fruits = { "apple", "banana", "cherry" };

foreach (string fruit in fruits)
{
    Console.WriteLine(fruit);
}

 

위 코드는 fruits 배열의 각 요소를 순회하며 출력합니다. 매우 간단하지만, 내부에서는 꽤 복잡한 일들이 벌어지고 있습니다.

 

foreach의 내부 동작 (while문으로 변환)

string[] fruits = { "apple", "banana", "cherry" };

IEnumerator enumerator = fruits.GetEnumerator();

while (enumerator.MoveNext())
{
    string fruit = (string)enumerator.Current;
    Console.WriteLine(fruit);
}

 

foreach 구문은 위 코드처럼 변환되어 동작합니다. MoveNext() 를 호출해서 다음 요소가 있는지 확인하고, 있으면 Current 로 값을 꺼내오는 구조입니다. 그리고 MoveNext() 가 존재할 경우, while () 순환을 반복합니다. 

 

GetEnumerator() 의 역할

foreach 가 가능하려면 대상 컬렉션이 GetEnumerator() 메서드를 제공해야 합니다.

배열의 경우, 내부적으로 다음과 같이 동작합니다:

public IEnumerator GetEnumerator()
{
    return new SZArrayEnumerator(this);
}

 

즉, GetEnumerator() 를 호출하면 SZArrayEnumerator 라는 내부 클래스를 생성하여 반환합니다. 이 클래스는 IEnumerator 인터페이스를 구현한 Enumerator 객체입니다.

 

IEnumerator 인터페이스란?

모든 Enumerator는 IEnumerator라는 인터페이스를 구현해야 합니다. 이 인터페이스는 다음과 같은 구조를 가집니다:

public interface IEnumerator
{
    object Current { get; }
    bool MoveNext();
    void Reset();
}

 

•   Current : 현재 요소를 반환

•   MoveNext() : 다음 요소로 이동 (이동 가능 여부를 bool로 반환)

•   Reset() : 처음 위치로 되돌림 (일반적으로 잘 사용되지 않음)

 

이 인터페이스가 존재하기 때문에 foreach 는 모든 컬렉션에서 Current와 MoveNext() 를 동일하게 사용할 수 있는 것입니다. 그리고 당연하게도 MoveNext() 내부의 구현은 컬렉션마다 클랙스 성격에 맞게 구현되어 있습니다. 즉, 자동으로 생성되는 코드는 아닙니다. 

 

실제 MoveNext() 구현 예시 (List의 경우)

public bool MoveNext()
{
    if ((uint)index < (uint)list._size)
    {
        current = list._items[index];
        index++;
        return true;
    }
    return false;
}

 

이처럼 각 컬렉션은 자기만의 Enumerator 클래스를 가지고 있고, MoveNext()와 Current 속성을 구현하여 순회를 가능하게 합니다.

 

요약

•   foreach()는 내부적으로 GetEnumerator()를 호출하여 Enumerator 객체를 생성합니다.

•   이 Enumerator는 IEnumerator 인터페이스를 구현하고 있어야 하며, MoveNext()와 Current를 반드시 포함해야 합니다.

•   배열, 리스트, 딕셔너리 등 모든 컬렉션은 자신에게 맞는 Enumerator를 직접 구현해놓았습니다.

•   결국 foreach는 while (MoveNext()) { Current } 구조와 동일합니다.

•   foreach 가 중간에 멈추지 못하고 한 방향으로 끝까지 순회만 가능한 이유는 바로 이 구조 때문입니다.