C#을 사용하면서 자주 사용되는 인터페이스 중 하나가 IEnumer~입니다. 이 인터페이스들은 사용자에게 직접 노출되기보다는 인터페이스의 구현체로 사용되기 때문에 이해하기 어려울 수 있습니다. 또한 컨테이너에만 사용되지 않기 때문에 더욱 어려운 면도 있습니다. 이러한 인터페이스 중 IEnumerator
와 IEnumerable
은 이름이 비슷하여 프로그래머들이 헷갈리기도 합니다.
이러한 인터페이스들은 어떤 역할을 하며, 어떤 용도로 사용되는 것일까요? 그리고 왜 사용처가 컨테이너에만 국한되지 않을까요? 능동적 주체와 수동적 주체로 2개로 나뉘어져 있는 이유도 궁금할 것입니다. 이제 열거 인터페이스를 분석해보며 이러한 의문을 해결해보도록 하겠습니다.
각 인터페이스의 이름을 보면 Enumerate 라는 단어에서 파생 됨을 짐작할 수가 있습니다. 그렇다면 Enumerate 가 의미하는 바가 무엇일까요? 사전적인 의미로는 열거하다 라는 의미를 지니고 있습니다. 이름과 의미를 보면 기존의 열거형 enum
을 떠올리기가 쉽습니다만, 의미적으로는 완전히 무관하지는 않더라도 기능적으로는 다소 거리가 있습니다. 기존의 열거형은 해당 범주에 속하는 것들을 나열하는 의미라면, Enumerate Interface 는 무언가를 꺼내서 나열한다는 의미에 가깝습니다.
보통 컨테이너를 사용하다 보면 foreach를 통하여 요소들을 하나씩 꺼내서 사용하는 경우가 많습니다. 이 때, 열거 인터페이스를 사용하게 됩니다. 여기서 IEnumerator
와 IEnumerable
2 개의 인터페이스가 사용 되는데, 서로 비슷한 듯 보여도 각자 상이한 역할을 수행합니다. 먼저, IEnumerable
은 해당 객체가 열거가 가능함을 나타냅니다. 그리고 IEnumerator
는 열거 행위를 수행하는 객체임을 나타냅니다.
그래서 IEnumerable
객체는 IEnumerator
를 반환하여 열거행위를 수행합니다. 이 인터페이스를 통하여 사용자가 정의한 객체 혹은 컨테이너가 foreach 구문에서 열거 행위를 할 수 있습니다. 그렇다면 각 인터페이스는 어떤 형태를 갖추고 있을까요? 먼저 코드를 살펴봅시다.
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
정말 단순합니다. 단 1개. IEnumerator
객체를 반환하는 GetEnumerator()
메소드를 구현하면 됩니다. 앞서 언급 했듯이, 본 객체는 열거가 가능함을 나타냅니다. 때문에 실질적인 열거 행위를 수행하지 않습니다. 그러면 이번에는 IEnumerator
에 대해서 살펴보도록 합시다.
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
본 인터페이스에는 도합 3가지의 메소드, Current
와 MoveNext
, Reset
이 존재합니다. Current
는 현재 컨테이너가 가리키고 있는 요소(element)를 반환합니다. 그렇다면 다른 2개의 메소드는 어떤 내용을 제공하는 것일까요? 실질적으로 열거 행위에 필요한 메소드는 MoveNext
입니다. 해당 메소드는 다음 객체를 가리키는 행위를 하며, 만약에 현재 마지막 요소를 가리키는 상태라면 false
를 반환합니다. 그래서 false
가 반환되면 열거는 중단됩니다.
실 구현 예시를 보면서 어떻게 활용이 가능한지 살펴보도록 합시다.
public class Agent
{
public int _number;
public string _name;
public Agent(int number, string name)
{
_number = number;
_name = name;
}
}
먼저, 컨테이너의 요소(element)가 되는 클래스 부터 정의합니다. 이에 대한 내용은 취향껏 정의해도 무방합니다.
public class Squad : IEnumerable
{
private Agent[] _members;
public Squad(Agent[] members)
{
_members = new Agent[members.Length];
for(var i = 0; i < members.Length; i++) _members[i] = members[i];
}
IEnumerator IEnumerable.GetEnumerator() => (IEnumerator) GetEnumerator();
public AssembleSquad GetEnumerator() => new AssembleSquad(_members);
}
그 다음으로는 열거가 가능한 객체를 구현합니다. 본 클래스에서 선형 리스트 데이터를 다루고 있으며, IEnumerable
인터페이스를 상속받아 열거 객체를 반환해줍니다.
public class AssembleSquad : IEnumerator
{
public Agent[] _members;
private int position = -1;
public AssembleSquad(Agent[] members)
{
_members = members;
}
public bool MoveNext()
{
position++;
return (position < _members.Length);
}
public void Reset() => position = -1;
object IEnumerator.Current => Current;
public Agent Current => _members[position];
}
마지막으로 열거자 객체입니다. 본 클래스는 IEnumerator
를 상속받아 요소를 반환하는 인터페이스를 정의하고 있습니다. 선형 구조의 컨테이너이기 때문에 인덱스 값을 통해 마지막 요소 여부를 체크하고 있습니다.
본 페이지에서 IEnumerator
와 IEnumerable
에 대해서 살펴보았습니다. 기본적으로 열거행위를 수행하는 인터페이스를 제공해주며, 단순히 컨테이너들의 열거 행위에만 사용되지 않습니다. 이와 관련한 내용들은 추후 Rx, Linq 및 코루틴에 대해서 다룰 때 같이 언급하도록 하겠습니다.