IEnumerator, IEnumerable의 의미

프로그래머 혹은 소프트웨어 엔지니어의 배경 지식은 컴퓨터 과학이고, 기초가 탄탄할 수록 게임이든 앱이든 잘 만들 수 있습니다. 이 글에서는 우리가 거의 매일 사용하고 있는 C#의 IEnumerator, IEnumerable의 탄생 과정을 살펴봄으로서 이해의 폭을 넓히고자 합니다.

일단 우리가 이해하고 있는 오브젝트는 단순히 게터/세터(getter/setter)의 집합이라는 사실에서 출발해 봅시다. 아래 Person 클래스는 Age 세터와 Name 게터를 정의하고 있습니다.

public class Person
{
   public int Age { set { ... } }
   public string Name { get { ... } }
}

여기서 클래스는 단순히 게터/세터를 모아놓은 것에 불과합니다.

그럼 게터는 뭘까요? C#의 람다식으로 표현하면 ()를 받아서 무엇인가를 리턴하는 함수입니다 ()는 인자가 없다는 뜻이므로, 아무 인자도 받지 않고 무엇인가를 만들어내는 게터는 수학적 의미의 함수는 아니고, 사이드 이펙트 함수입니다. 부를 때마다 값을 계속 만들어내는 일종의 생산자라고도 생각할 수 있습니다.

() => A

() => string의 의미에 대해 다시 살펴봅시다. 이 게터의 타입은 Func<string>이고, 호출을 하면 매번string의 인스턴스를 리턴한다는 뜻입니다. 하지만 엄밀히 말하면 C#의 타입 시스템은 거짓말을 하고 있습니다. 이 함수는 string만 리턴할 수 있는 게 아니라 사실은 예외를 던질 수도 있기 때문입니다. 혹은 이 함수가 프로그램을 종료시킬 수도 있습니다.

() => { throw new Exception(); }

그런데 게터는 어디서 나오는 걸까요? 게터를 얻어 오는 방법은 간단합니다. 다소 말장난 같긴 하지만, 게터를 리턴하는 게터를 정의하면 됩니다. 예를 들어 () => string 게터를 리턴하는 게터는 아래와 같이 정의할 수 있습니다. 물론 앞서 살펴본 것처럼 () => stringstring만 리턴하는 것은 아니고, 예외를 던지거나 프로그램을 종료시킬 수도 있습니다.

() => (() => string)

람다식은 알아보기 힘들기 때문에 다시 클래스 표기법으로 돌아가겠습니다. C#은 독립적인 메소드가 없기 때문에 인터페이스를 정의하고, 메소드를 정의해야 합니다. 게터를 리턴하는 게터와, 게터를 각각 별도의 인터페이스로 정리해 보겠습니다.

public interface IEnumerable<out T>
{
    IEnumerator<T> GetEnumerator();
}

public interface IEnumerator<out T>
{
    bool MoveNext();
    T Current { get; }
}

재미있게도 게터를 리턴하는 게터는 IEnumerable이고, 게터는 IEnumerator입니다. 왜 이렇게 될까요?

일단 게터를 먼저 살펴보면, MoveNext() 함수가 하는 일이 게터와 정확히 일치함을 알 수 있습니다.MoveNext()true를 리턴하면 다음 값을, false를 리턴하면 종료를 뜻합니다. 또한 타입에는 표현되지 않았으면 예외를 던질 수도 있습니다. 참고로 Current 프로퍼티는 사이드 이펙트가 없으므로 언제든 호출할 수 있습니다.

IEnumerable은 게터에 해당하는 IEnumerator를 리턴하는 GetEnumerator() 메소드를 정의하고 있으므로, 게터를 리턴하는 게터임을 알 수 있습니다.

이런 관계를 어떻게 해석해야 할까요? 앞서 () => stringstring 값을 생성하는 일종의 생성자라고 표현했습니다. 우리가 일상적으로 쓰는 사이드 이펙트가 있는 함수라는 게 호출할 때마다 새로운 값을 리턴해 주기 때문입니다. 또 생각해 보면 IEnumerator<string>라는 것도 일종의 생산자입니다. 부를 때마다 새로운 값을 리턴해 주기 때문입니다. () => string도 예외를 던지거나 종료될 수 있습니다. IEnumerator<string>도 마찬가지로 예외를 던지거나 종료될 수 있습니다. 따라서 () => string이라는 건 IEnumerator<string>과 하는 일이 다르지 않습니다.

바꿔 말하면 이론적으로는 IEnumerable이라는 건 우리가 늘 사용하고 있는 게터를 인터페이스로 정의한 것에 지나지 않습니다. 또한 이런 게터를 얻어올 수 있는 방법을 IEnumerator라는 인터페이스로 정의한 것이고요. 물론 그래서 의미가 없다는 뜻은 아닙니다. .NET에 존재하는 수많은 콜렉션 클래스들을 동일한 인터페이스로 사용할 수 있게 되었고, 그래서 LINQ와 같은 기술도 등장할 수 있게 되었기 때문입니다. 다만, 우리가 뭔가 새로운 것으로 생각하는 것이 사실은 우리가 늘 쓰고 있는 개념에 새로운 옷을 입힌 것과 다르지 않다는 것을 이야기하고 싶었습니다.

One thought on “IEnumerator, IEnumerable의 의미

  1. Pingback: IEnumerator, IEnumerable의 의미 | 서광열의 C# 스쿨

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s