블로그

IEnumeration , IEnumerator 에 대해 본문

카테고리 없음

IEnumeration , IEnumerator 에 대해

confielder 2024. 3. 26. 02:11

IEnumeration , IEnumerator의 역할


IEnumeration과 IEnumerator은 foreach 반복을 시키기 위해 꼭 정의해줘야 하는 인터페이스다.

우리는 배열에서 혹은 ArrayList , Stack과 같은 컬렉션에서 Foreach를 통해, 배열의 요소에 접근할 수 있었다.

 

그 이유는 배열과(System.Array), ArrayList , Stack의 클래스가 IEnumeration라는 인터페이스를 상속받았기 때문이다.

 

따라서 우리가 따로 클래스를 만들고, 이 클래스로 foreach문을 실행시키기 위해선 저 클래스들과 똑같이 IEnumeration을 상속받아서 따로 구현해줘야 한다.

 

IEnumerable의 역할


IEnumeration 인터페이스 내부에는 단 하나의 함수가 존재한다.

 

namespace System.Collections
{
    public interface IEnumerable
    {
        IEnumerator GetEnumerator();
    }
}

 

GetEnumerator는 함수의 이름 그대로 Enumerator를 리턴 받는 함수인데, 이 함수는 최종적으로 foreach 문을 통해 컬렉션에 요소들을 얻어올 때 필요한 Enumerator를 반환한다.

 

이 Enumerator 클래스 내부에는 Object Current라는 프로퍼티가 들어있다. 이 프로퍼티가 우리가 얻고자 하는 요소이다.

 

IEnumerator의 역할


IEnumerator는 foreach문을 돌리기 위해 필요한 속성들을 정의한다. 내부 코드를 보자면,

namespace System.Collections
{
    public interface IEnumerator
    {
        object Current { get; }

        bool MoveNext();
        void Reset();
    }
}

 

object Current: 앞서 말한 대로 컬렉션의 현재 요소를 뜻한다. 

 

MoveNext(): 컬렉션의 인덱스를 순차적으로 증가시킨다. 이 인덱스가 이동이 성공하면 true, 컬렉션의 끝을 지난 경우에는 false를 반환한다.

 

Rest(): 컬렉션의 맨 앞으로 이동한다. 

 

따라서 for(int i = 0; i <array.length(); i++)와 같은 의미를 지닌다고 이해하면 쉽다.

 

이 IEnumerator 인터페이스가 결과적으로 foreach 문의 구현을 담당한다고 보면 된다. 그래서 실제로 IEnumerable을 클래스에서 상속받지 않고, GetEnumerator함수만 구현해 주면 해당 클래스는 foreach 문으로 요소에 접근할 수 있다. 이때 GetEnumerator가 리턴하는 값이 foreach 문에 요소에 저장되는 값이라고 했는데, 이때 리턴은 IEnumerator를 상속받는 Enumerator 전용 클래스를 만들어서 리턴하던가, 아니면 yield return을 통해 리턴 받을 수 있다.  

 

Enumerator의 간단한 예시 (yield)


class MyEnum
{
    int[] numbers = { 1, 2, 3 };

    public IEnumerator GetEnumerator()
    {
        yield return numbers[0];
        yield return numbers[1];
        yield break;
    }
}

public class enumTest : MonoBehaviour
{    
	private void Start()
    {
        MyEnum list = new MyEnum();
        foreach (int i in list)
        {
            Debug.Log(i);
        }
    }    
}

 

이 코드는 클래스를 배열로 선언하고 foreach를 실행시키는 예시이다. 원래는 불가능했던 foreach가 GetEnumerator() 함수로 인해 가능해졌다. 여기서 MyEnum클래스는 IEnumerable 인터페이스를 상속받지 않은 것을 볼 수 있다.

 

원래는 IEnumerable의 GetEnumerator() 함수를 통해 Enumerator을 리턴해야 가능했던 일이 yield return을 통해 생략된 것이다. yield가 없다면 IEnumerator을 반환해야 하는데 int를 반환하게 된다. 그럼 이 yield return에 대해 알아보자.

 

yield return


yield는 컴파일러가 자동으로 IEnumerator을 구현한 객체를 리턴해주기 때문에 GetEnumerator() 함수를 구현한 클래스 내부의 값을 그냥 "yield return" 해주면 된다. 

 

따라서 Current , MoveNext() , Reset() 등의 함수를 따로 구현하지 않아도 되는 것이다. IEnumerable의 GetEnumerator() 함수 또한 우리가 구현했기 때문에 IEnumerable을 상속받지 않아도 되는 것이다.

 

만약 yield return을 사용하지 않았다면, 우리는 GetEnumerator() 함수를 만들기 위해 IEnumerable을 상속받고, IEnumerable은 IEnumerator가 구현된 객체를 필요로 하기에 IEnumerator전용 클래스를 만들어야 한다.


 

또 하나의 간단한 예시가 있다. 이 코드에서는 한 클래스에 IEnumerable과 IEnumerator 둘 다 구현한다.(좋은 건 아님)

class MyList : IEnumerable, IEnumerator
{
    private int[] array;
    int position = -1;

    public MyList()
    {
        array = new int[3];
    }

    public int this[int index]
    {
        get
        {
            return array[index];
        }

        set
        {
            if (index >= array.Length)
            {
                Array.Resize<int>(ref array, index + 1);
                Console.WriteLine($"Array Resized : {array.Length}");
            }

            array[index] = value;
        }
    }


    public object Current
    {
        get
        {
            return array[position];
        }
    }


    public bool MoveNext()
    {
        if (position == array.Length - 1)
        {
            Reset();
            return false;
        }

        position++;
        return (position < array.Length);
    }


    public void Reset()
    {
        position = -1;
    }


    public IEnumerator GetEnumerator()
    {
        return this;
        
    }
}

 

 

정리


IEnumerable은 IEnumerator을 반환하는 역할을 한다. IEnumerator는 컬렉션을 순차적으로 이동하면서 컬렉션의 요소인 Current를 갖게 된다. 결과적으로 우리가 필요한 값은 이 Current이다. 

 

yield return을 통해 우리는 요소를 직접적으로 리턴할 수 있다. 또한 IEnumerable과 IEnumerator을 구현하지 않아도 된다.

yield 가 어떤 식으로 동작하는지는 여기서 따로 설명하지는 않는다. 이 글은 비교적 쉽게 IEnumerable, Enumerator을 이해하는데 중점을 뒀기 때문이다.