안녕하세요.

 

지난번 포스팅에서는 IEnumerator와 IEnumerable를 이해하기 위해서 먼저 인터페이스에 대해서 살펴보았습니다. 

 

혹시 아직 보시지 못하신 분이 있으시다면 이 전 포스팅을 읽고 오시는 것을 추천드립니다. 

 

2024.05.03 - [유니티 기초 스킬] - 코루틴(Coroutine)과 IEnumerator을 이해하기 위해(1) - Interface

 

이번 포스팅에서는 지난 시간에 이야기한 인터페이스에 대한 이해를 바탕으로 IEnumerator와 IEnumerable의 실제적인 기능들을 이해해보려고 합니다.

 

IEnumerator와 IEnumerable의 기능

인터페이스가 일련의 기능을 명시해 놓은 약속이라는 것을 알았다면, 이제 IEnumerator와 IEnumerable는 어떤 기능을 위해서 만들어 졌는지 이해할 필요가 있습니다.

 

IEnumerator와 IEnumerable는 둘 다  배열이나 리스트 딕셔너리 같은 컬랙션을 위해서 기능하도록 만들어진 인터페이스입니다.

 

IEnumerator는 컬렉션에서 3가지 기능을 구현할 수 있도록 만들어졌습니다.

 

(1) 컬렉션의 다음 요소로 접근하는 기능 : bool MoveNext()

 

(2) 현재 요소의 값을 반환하는 기능 :  object Current{get;}  

 

(3) 접근하고 있는 요소의 순서를 처음으로 돌리는 기능 :  Reset()

 

어떤 컬랙션의 요소가 오든지 IEnumerator의 기능은 이 세가지 입니다. 

 

예를 들어 보겠습니다. 

 

int[] number = { 0, 1, 2, 3, 4, 5 };

 

int로 구성된 number라는 배열을 하나 만들었습니다. 

 

만일 이 배열에 IEnumerator기능을 사용한다면 이런 기능을 하게 될겁니다. 

 

- 먼저 bool MoveNext() 명령을 통해서 첫번째 인덱스에 접근합니다. ( number[0])

- 그 다음 object Current{get;} 명령을 하면 현재 접해 있는 인덱스의 요소를 반환해줍니다. ( number [0] = 0, 따라서 0을 반환)

- 그 후 다시 bool MoveNext() 명령을 통한다면 다음 인덱스로 넘어갈 수 있습니다. (number[1])

- 또 그다음 그 다음  object Current{get;} 명령을 하면 현재 접해 있는 인덱스의 요소를 반환해줍니다. ( number [1] = 1, 따라서 1을 반환)

.

.

.

- 마지막 인덱스 요소까지 왔을때 더 이상 인덱스가 없다면 bool  MoveNext()는 false를 반환을 반환하고, 여기서 Reset()을 하면 다시 가장 처음 인덱스 요소보다 하나 전 단계로 접근합니다.

 

이런 방식을 자세히 알 필요는 없지만 이런 기능으로 유추 할 수 있는 것은 IEnumerator가 언제 사용되는가 하는 것입니다. 

 

IEnumerator는 어떤 컬렉션의 순서대로 요소를 접근해서 그 값을 참조하고 싶을때 사용합니다. 요소 중 일부만 빼내어 사용하는 그런 방식이 아니라 단순이 처음부터 끝까지 차례대로 같은 동작을 반복할때 좋겠죠?

 

이런 방식을 사용하는 반복문이 하나 있습니다.

 

바로 forech입니다. 우리가 forech를 사용할 때 나타나는 기능이 바로 IEnumerator 인터페이스에 명시된 기능입니다. 이 IEnumerator의 기능을 가져와서 forech는 구현합니다. 

 

하지만 IEnumerator이러한 이렇게 순차적으로 뭔가가 있으면 각 요소에  순서대로 접근해 값을 주는 정도의 기능만을 담당하고 있을 뿐이지 그 "순차적으로 뭔가"가 어떤 형식인지를 정의하고 있지는 않습니다. 

 

그래서 필요한 것이 IEnumerable 입니다. 

 

IEnumerable은 어떤 객체 혹은 대상이 '순차적으로 접근할 수 있는 구조'로 되어있는지를 인식하는 기능을 가지고 있습니다. 

 

이떤 대상이 '순차적으로 접근할 수 있는 구조'로 되어 있는지 판단해주는 기능을 한다기 보다는 IEnumerable 변수에 담을 수 있다면 그 대상에는 반드시 순차적으로 접근할 수 있는 요소가 존재하는 구조로 되어 있다는 것을 뜻한다는 의미입니다. 

 

그렇기 때문에 IEnumerable이 가진 기능은 단순합니다. IEnumerable 변수 안에 포함된 대상 혹은 객체가 가진 구조를 이용할 수 있도록 IEnumerator의 기능을 불러오는 기능만을 합니다. 

 

실제 IEnumerable 인터페이스는 이렇게 표현되어 있습니다.

public interface IEnumerable
    {
        // 컬렉션을 반복하는 열거자를 반환합니다.
        IEnumerator GetEnumerator();
    }

 

IEnumerable가 하는 일은 객체에 포함된  열거적 요소를 사용할 수 있는 기능을 불러오는 기능입니다. 외부에서 사용할 수 있게 IEnumerator을 연결시켜주는 역할만 한다고 보시면 되겠습니다. 

 

다시 말해 어떤 기능을 해서 결과물을 만들어내는 주체( IEnumerator )가 있고 그 결과물을 외부로 전달해서 외부에서 그것을 가공할 수 있게 해주는 매개체역할( IEnumerable )이 있는 겁니다. 

 

그래서 이 둘은 거의 함께 합니다. 

 

이것도 어려우니까 예를 들어보겠습니다. 

 

        int[] number = new int[] { 0, 1, 2, 3 }; 

        // 1. IEnumerable가 array에 구현되어 있기 때문에 변수로 할당하는 것이 가능.
        IEnumerable Inum = number; 
       
       //2. IEnumerable 안에는 GetEnumerator()가 존재하기 때문에 
       //    array에 존재하는 IEnumerator를 가져다 ienumerator에 할당시킴.  
        IEnumerator ienumerator = Inum.GetEnumerator(); 

        ienumerator.MoveNext(); //arrry안에 있는 첫번째 요소 접근 
        Debug.Log(ienumerator.Current); // 접근한 요소 반환해서 출력.
       
        ienumerator.MoveNext();// arrry안에 있는 두번째 요소 접근 
        Debug.Log(ienumerator.Current); // 접근한 요소 반환해서 출력.
        
        ienumerator.MoveNext();// arrry안에 있는 세번째 요소 접근 
        Debug.Log(ienumerator.Current);// 접근한 요소 반환해서 출력.
        
        ienumerator.MoveNext();// arrry안에 있는 네번째 요소 접근 
        Debug.Log(ienumerator.Current);// 접근한 요소 반환해서 출력.

 

이 예제를 설명하기 전에 C#에서 제공하는 array(배열)에 대해 살짝 이해하고 넘어가야 합니다. 

 

배열은 컬렉션의 일부로 C#에서 구현해놓은 키워드입니다. 정해진 길이의 데이터를 인덱스 요소로 관리 하기 위해서 만든 클래스입니다. 

 

그런데 이런 array를 비롯한 모든 컬렉션과 컬렉션<T>의 클래스에는 IEnumerable과 IEnumerator이 구현되어 있습니다.(상속되어 있습니다.)

 

따라서 array나 다른 컬렉션을 사용할때에는 그 안에 반드시 IEnumerable과 IEnumerator의 요소들이 포함되어 있는 겁니다. 우리가 평소 사용할때 보이지는 않지만 IEnumerable과 IEnumerator의 요소들을 꺼내서 사용할 수 있으며 그것을 사용해서 구현한 것이 forech입니다. 

 

따라서 forech를 사용할 때 입력하는 변수는 반드시  System.Collections 이나 System.Collections.Generic에 포함된 컬렉션 이어야 합니다. 

 

이제 위의 예제를 살펴보겠습니다.

       int[] number = new int[] { 0, 1, 2, 3 }; 

       IEnumerable Inum = number;

 

먼저 int 0,1,2,3의 요소를 가진 배열 number를 선언했습니다.

 

그리고 IEnumerable 의 타입을 가진 Inum이라는 변수에 number 배열을 할당합니다. 

 

이게 가능한 이유는 지난시간 설명했던 인터페이스 예제에서  Weapon 클래스의 객체인 sword 객체를  IDurability 데이터 타입의 변수에 할당할 수 있던것과 같은 이유입니다. array 클래스에는 IEnumerable 인터페이스가 구현되어 있기 때문에 가능합니다.

 

 IEnumerable 타입의 변수에 담을 수 있다 혹은 IEnumerable 타입으로 반환할 수 있다는 것은 그 대상 혹은 객체가 순차적으로 접근할 수 있는 요소들을 반드시 포함한다는 의미입니다. 

 

그렇기 때문에 IEnumerable의 타입의 변수를 통해 할 수 있는 혹은 하려는 일은 하나입니다. 순차적인 각 요소에 접근해 값을 받아오는 일입니다.

 

그리고 그일을 가능하게 하기 위해서 IEnumerable 인터페이스가 구현하는 기능은 딱 하나입니다.

 

순차적인 요소에 접근해 요소를 반환하는 기능을 가진  IEnumerator를 불러오는 기능 바로 GetEnumerator()입니다. 

 

따라서 IEnumerable의 데이터 타입을 가진 Inum이 사용할 수 있는 참조형태는 딱 하나입니다. 

 

Inum.GetEnumerator() 이거죠. 그럼 Inum안에 있는 IEnumerator의 기능을 실행시킬 수 있게 되는 겁니다. 

 

그리고 그 실행시키는 기능 자체를 IEnumerator 데이터 타입의 변수 ienumerator  에 담습니다. 

 IEnumerator ienumerator = Inum.GetEnumerator();

 

그럼 이제 ienumerator  의 프로퍼티를 사용하면 IEnumerator의 세가지 기능을 사용할 수 있게 됩니다. 

 

ienumerator.MoveNext()

ienumerator .Currunt;

ienumerator .Reset();

 

이렇게 말이죠.

 

 ienumerator는  IEnumerator의 기능을 가지고 있는 클래스입니다. 따라서 위의 세가지 기능을 사용한다면 ienumerator에 할당되어 있는 클래스 객체인 number에서 이 기능이 실행되는 겁니다. 

 

        ienumerator.MoveNext(); //arrry안에 있는 첫번째 요소 접근 
        Debug.Log(ienumerator.Current); // 접근한 요소 반환해서 출력.
       
        ienumerator.MoveNext();// arrry안에 있는 두번째 요소 접근 
        Debug.Log(ienumerator.Current); // 접근한 요소 반환해서 출력.
        
        ienumerator.MoveNext();// arrry안에 있는 세번째 요소 접근 
        Debug.Log(ienumerator.Current);// 접근한 요소 반환해서 출력.
        
        ienumerator.MoveNext();// arrry안에 있는 네번째 요소 접근 
        Debug.Log(ienumerator.Current);// 접근한 요소 반환해서 출력.

 

 

사실 반복문이기 때문에 forech를 사용해도 되지만 어떤 식으로 동작하는지 알기 위해서 나열해 봤습니다. 

 

배열 클래스 number가 할당 된 ienumerator에서 MoveNext()를 실행하면 배열의 첫번째 요소에 접근합니다.

( MoveNext()를 사용하기 전에 접근해 있는 요소는 -1요소로 null이라고 나옵니다.)

그다음 ienumerator.Current 실행을 통해 접근한 첫번째 요소를 출력합니다. 0이 출력됩니다.

 

그 다음은 설명하지 않아도 괜찮겠죠?

 

지금까지 IEnumerator와 IEnumerable 인터페이스의 기능을 이해하고 이러한 기능을 C# 컬렉션에서 어떤 식으로 이용하는지 살펴봤습니다.

 

다음 시간에는 IEnumerator와 IEnumerable의 이해를 바탕으로 코루틴을 사용할때 함께 사용되는 yield return에 대해 이해해보는 시간을 갖도록 하겠습니다. 

 

 

감사합니다. 

 

+ Recent posts