Post

Enumerator

C# Programming

Enumerator

Enumerator


Enumerator 는 컬렉션의 요소들을 순차적으로 접근할 수 있게 해주는 반복자 패턴을 구현한 객체이다.

IEnumeratorIEnumerable 인터페이스를 통해 지원하며, Unity 에서는 코루틴에서도 활용된다.


Interface

IEnumerable은 컬렉션이 반복 가능함을 나타내는 인터페이스 이다.

1
2
3
4
5
// IEnumerable 인터페이스
public interface IEnumerable
{
    IEnumerator GetEnumerator();
}


IEnumerator는 실제 반복 작업을 수행하는 인터페이스이다.

1
2
3
4
5
6
7
// IEnumerator 인터페이스
public interface IEnumerator
{
    object Current { get; }  // 현재 요소
    bool MoveNext();         // 다음 요소로 이동
    void Reset();            // 처음으로 리셋
}


컬렉션 클래스는 IEnumerable, GetEnumerator 가 반환하는 열거자 클래스엔 IEnumerator 을 상속받는다.


응용


foreach

foreach 반복문은 내부적으로 IEnumerator를 사용한다.

foreach 를 사용하기 위해선, 해당 클래스가 내부적으로 GetEnumerator 메서드가 구현되어 있어야한다.

또한 해당 클래스 내부엔 MoveNext(), Current, Reset() 가 반드시 정의되어 있어야 한다.

1
2
3
4
5
6
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

foreach (int number in numbers)
{
    Debug.Log(number);
}


위 코드는 내부적으로 아래와 같이 동작한다.

1
2
3
4
5
6
7
8
9
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// List 는 내부적으로 GetEnumerator 가 구현되어 있다.
IEnumerator enumerator = numbers.GetEnumerator();
while (enumerator.MoveNext())
{
    int number = (int)enumerator.Current;
    Debug.Log(number);
}


구현

커스텀으로 컬렉션을 만들어 IEnumerableIEnumerator 를 직접 구현한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Enumerator_Practice : MonoBehaviour
{
    class MyList : IEnumerable // 클래스는 IEnumerable 상속
    {
        class enumerator : IEnumerator // IEnumerator 상속
        {
            private int index = -1;
            private int[] num = { 1, 2, 3, 4, 5 };
            
            public object Current { get => num[index]; } // 구현 필수

            public bool MoveNext() // 구현 필수
            {
                if (index == num.Length - 1)
                    return false;

                index++;
                return true;
            }
            public void Reset() // 구현 필수
            {
                index = -1;
            }
        }
        public IEnumerator GetEnumerator() // IEnumerable 에 의해 반드시 구현해야함.
        {
            enumerator enumerator = new enumerator();
            return enumerator; // 프로퍼티 및 메서드 반환
        }
    }
    
    void Start()
    {
        MyList list = new MyList();

        // foreach 문은 list.GetEnumerator() 호출
        // 반환된 enumerator 객체로 MoveNext(), Current 반복 호출
        foreach (int i in list)
        {
            Debug.Log(i);
        }
    }
}


yield return


yield return 키워드를 사용하면 더 간단하게 IEnumerator를 구현할 수 있다.

yield 문이 사용된 메서드를 컴파일러는 IEnumerable, IEnumerator 코드로 치환하여 내부적으로 구현한다.

따라서 MoveNext, Current, Reset 같은 코드를 직접 작성하지 않아도 된다.

GetEnumerator() 안에 반복적으로 yield return 을 작성하면, 자동으로 값을 하나씩 반환하는 열거자가 만들어진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Enumerator_Ex : MonoBehaviour
{
    public class MyList
    {
        public IEnumerator GetEnumerator()
        {
            int[] num = { 1, 2, 3, 4, 5 };
            int index = -1;

            while (index < num.Length - 1)
            {
                index++;
                yield return num[index];
            }
        }
    }
    void Start()
    {
        MyList myList = new MyList();
        
        foreach(int i in myList)
        {
            Debug.Log(i);   
        }
    }
}


return 은 메서드를 즉시 종료하고, 호출자에게 값을 반환하며 이후엔 코드실행이 불가하다는 특징이 있다.

반면 yield return 은 현재 값을 호출자에게 반환하고, 그 다음 실행 지점에서 대기, 다음 호출 시 이어서 실행된다.


Reference


-

-