Unity C# IEnumerator vs IEnumerable

 

본론에 앞서 IEnumerator 에 대한 이야기부터 해보자.

IEnumerator는 유니티를 다루는 사람이라면 코루틴 덕분에 익숙한 키워드 일 것이다.

 

코루틴 앞에 붙여야 하는 자료형으로 의미를 모르고 외웠을 수 있다.

하지만 코루틴이 정확히 어떤 일을 하는지 알고 있고, C# 쪽을 심화해서 학습했다면

IEnumerator가 의미하는 바를 알 것이다.

 

#. IEnumerator란?

MSDN에서는 다음과 같이 설명한다. 

'IEnumerator(열거자)란 컬렉션을 단순하게 반복할 수 있도록 지원합니다.'

 

IEnumerator는 System.Collections 네임스페이스에 속한 interface이며,

내부 구현으로는 현재 위치를 뜻하는 Current;와 다음 위치까지 이동하는 bool MoveNext(); 등이 있다.

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

 

#. IEnumerator vs IEnumerable

IEnumerator와 비슷한 이름의 IEnumerable는 무엇일까?

'IEnumerable는 컬렉션에서 열거자를 노출합니다.'

 

간단하게 말하면 IEnumerator를 리턴 시키는 Getter의 역할을 하는 인터페이스다.

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

 

흔히 사용하는 foreach에서 매 루프마다 가져오는 item은 어떻게 가져올 수 있는가?

대상이 되는 컬렉션이 IEnumerable을 구현하여 GetEnumerator()가 구현되어있기 때문이다!

foreach (var item in collection)
{
    // item은 무엇이 넘어오는가?
}

 

때문에 List 등의 컬렉션에는 IEnumerable을 구현하는 것을 확인할 수 있다.

 

#. IEnumerator 활용하기

IEnumerator 인터페이스를 활용하여 다음과 같은 코드를 작성할 수 있다. 

void Start()
{
    Debug.Log("테스트 시작");
    var temp = TestEnumerator();
    Debug.Log($"현재 값 : {temp.Current}");
    temp.MoveNext();
    Debug.Log($"현재 값 : {temp.Current}");
    temp.MoveNext();
    Debug.Log($"현재 값 : {temp.Current}");
}

IEnumerator TestEnumerator()
{
    yield return 1;
    yield return 2;
}

 

최초 MoveNext()를 하기 전까지는 Current()는  null을 리턴한다. 

yield return을 int로 했지만 제네릭 버전이 아니라 object로 박싱 하기 때문에 0이 아니라 null이 출력된 것.

 

코드를 IEnumerator<int> TestEnumerator() 로 작성해보면

위와 같이 최초의 Current가 int의 default값인 0을 리턴하는 것도 확인할 수 있다.

 

 

#. IEnumerator 는 Lazy이다

IEnemerator의 값은 yield return에 도달하는 순간에 결정된다.

즉 값을 최종 계산해서 가지고 있는게 아니라 값을 계산하는 방법을 가지고 있을 뿐이다.

public int test = 1;

void Start()
{
    Debug.Log("테스트 시작");
    var temp = TestEnumerator();

    Debug.Log($"현재 값 : {temp.Current}");
    temp.MoveNext();
    Debug.Log($"현재 값 : {temp.Current}");
    test = 2; // 중간에 값을 바꾼다.
    temp.MoveNext();
    Debug.Log($"현재 값 : {temp.Current}");
}

IEnumerator<int> TestEnumerator()
{
    yield return test;
    yield return test;
}

test = 2; 를 통해 중간에 변수의 값을 바꾼 경우 바뀐 값을 리턴하고 있다.

 

IEnumerator 구현의 MoveNext()에서 알 수 있듯, 매 시점마다 yield까지 위치를 이동하면서 값을 리턴하고 있다.

 

'현재'와 '다음을 구한다'라는 단순한 구현으로 되어있기 때문에 중간에 외부에서 요소를 변경하는 경우에 취약한데,

누구나 foreach를 쓰다가 중간에 요소 삭제를 한 경우 에러가 발생했던 경험이 있을 것이다.

이 또한 Lazy특성 때문에 발생하는 이슈이다.  

 

Lazy 특성 덕분에 IEnumerable은 이론상 무한대의 값을 리턴 가능하다.

(미리 값을 담아두는 방식이었다면 메모리가 남아나지 않았을 것이다.)

 

Lazy는 중요한 특성이며, IEnumerable 및 IEnumerator를 사용할 때 이로 인해 버그가 발생되는 경우가 많으니 숙지하자.

 

 

구체적인 예시는 다음 글에서 이어서 작성.

2022.12.07 - [Unity/프로그래밍] - Unity C# 코루틴 -> IEnumerator.Next()로 리팩토링

 

Unity C# 코루틴 -> IEnumerator.Next()로 리팩토링

2022.12.07 - [Unity/프로그래밍] - Unity C# IEnumerator vs IEnumerable Unity C# IEnumerator vs IEnumerable 본론에 앞서 IEnumerator 에 대한 이야기부터 해보자. IEnumerator는 유니티를 다루는 사람이라면 코루틴 덕분에 익

mentum.tistory.com

 

댓글

Designed by JB FACTORY