Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

nomad-programmer

[Programming/C#] 일반화 클래스 : IEnumerable<T>, IEnumerator<T> 본문

Programming/C#

[Programming/C#] 일반화 클래스 : IEnumerable<T>, IEnumerator<T>

scii 2020. 9. 20. 04:08

foreach를 사용할 수 있는 클래스를 만드려면 IEnumerable 인터페이스와 IEnumerator 인터페이스를 상속하고 이들에게 선언되어 있는 메소드와 프로퍼티를 구현해야 한다.

일반화 클래스도 IEnumerable과 IEnumerator 인터페이스를 상속하여 이들의 메소드와 프로퍼티를 구현하면 일반은 foreach를 통해 순회를 할 수 있지만, 요소를 순회할 때마다 형식 변환을 수행하는 오버로드가 발생한다는 문제가 있다.
성능을 위하여 기껏 일반화를 통해 형식 변환을 제거하였더니 foreach 구문에서 형식 변환을 일으켜 성능을 저하시키면 너무 바보 같은 일일것이다.

System.Collections.Generic 네임스페이스에는 이 문제를 풀 수 있는 열쇠를 가지고 있다. 바로 IEnumerable, IEnumerator의 일반화 버전인 IEnumerable<T>, IEnumerator<T> 인터페이스다. 이들을 상속하여 메소드와 프로퍼티를 구현하면, 형식 변환으로 인한 성능 저하가 없으면서도 foreach 순회가 가능한 클래스를 작성할 수 있다.

메소드 설명
IEnumerator GetEnumerator() IEnumerator 형식의 객체를 반환 (IEnumerable로부터 상속받은 메소드)
IEnumerator<T> GetEnumerator() IEnumerator<T> 형식의 객체를 반환

IEnumerable<T> 인터페이스는 GetEnumerator() 메소드를 두 개나 갖고 있다. 메소드들은 이름이 같지만 반환 형식이 다르다.
IEnumerator를 반환하는 버전의 GetEnumerator() 메소드는 IEnumerable<T> 인터페이스가 IEnumerable 인터페이스로부터 상속을 받아 얻어온 것이고, IEnumerator<T>를 반환하는 버전은 IEnumerable<T>에서 새로 선언된 메소드다. 그래서 이 두 가지 버전을 모두 구현해줘야 한다.

IEnumerator<T>의 메소드와 프로퍼티

메소드/프로퍼티 설명
boolean MoveNext() 다음 요소로 이동. 컬렉션의 끝을 지난 경우 false, 이동이 성공한 경우 true를 반환한다.
void Reset() 컬렉션의 첫 번째 위치의 앞으로 이동. 첫 번째 위치가 0번일 때, Reset()을 호출하면 -1번으로 이동한다. 첫 번째 위치로의 이동은 MoveNext()를 호출한 다음 이루어진다.
object Current { get; } 컬렉션의 현재 요소를 반환한다. (IEnumerator로부터 상속받은 프로퍼티)
T Current { get; } 컬렉션의 현재 요소를 반환한다.

IEnumerator<T>도 Current 프로퍼티가 두 가지 버전을 갖고 있다. 하나는 IEnumerator로부터 상속받은 버전, 또 다른 하나는 IEnumerator<T>에서 선언된 일반화를 지원하는 버전이다. 이 두 가지 모두 구현해줘야 한다.

IEnumerable<T>, IEnumerator<T>는 형식 매개 변수를 제외하면 IEnumerable, IEnumerator와 같다.

IEnumerable<T>, IEnumerator<T> 일반환 클래스 예제

using System;
using System.Collections;
using System.Collections.Generic;

namespace test
{
    public class MyArray<T> : IEnumerable<T>, IEnumerator<T>
    {
        private T[] _array;
        private int _position = -1;

        public MyArray() => _array = new T[3];

        public int Length()
        {
            return _array.Length;
        }

        public T this[int index]
        {
            get { return _array[index]; }
            set
            {
                if (index >= _array.Length)
                {
                    Array.Resize<T>(ref _array, index + 1);
                    Console.WriteLine($"Resizing Array : {_array.Length}");
                }

                _array[index] = value;
            }
        }

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

        object IEnumerator.Current
        {
            get { return _array[_position]; }
        }

        public T Current
        {
            get { return _array[_position]; }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            for (int i = 0; i < _array.Length; i++)
            {
                yield return (_array[i]);
            }
        }

        public IEnumerator<T> GetEnumerator()
        {
            for (int i = 0; i < _array.Length; i++)
            {
                yield return (_array[i]);
            }
        }

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

            _position++;
            return (_position < _array.Length);
        }

        public void Dispose()
        {
        }
    }

    internal class Program
    {
        public static void Main(string[] args)
        {
            MyArray<string> str_arr = new MyArray<string>();
            str_arr[0] = "abc";
            str_arr[1] = "def";
            str_arr[2] = "ghi";
            str_arr[3] = "jkl";
            str_arr[4] = "mno";

            foreach (string str in str_arr)
            {
                Console.WriteLine(str);
            }
            Console.WriteLine();
            
            MyArray<int> int_arr = new MyArray<int>();
            int_arr[0] = 0;
            int_arr[1] = 1;
            int_arr[2] = 2;
            int_arr[3] = 3;
            int_arr[4] = 4;

            foreach (int num in int_arr)
            {
                Console.WriteLine(num);
            }
        }
    }
}


/* 결과

Resizing Array : 4
Resizing Array : 5
abc
def
ghi
jkl
mno

Resizing Array : 4
Resizing Array : 5
0
1
2
3
4

*/
Comments