IEnumerable<T>, IEnumerator<T> 구현/foreach를 사용할 수 있는 일반화 클래스 만들기

00·2024년 12월 23일

C#

목록 보기
64/149

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

/*
배경:
IEnumerable인터페이스를 상속해서 foreach를 사용할 수 있는 클래스를 만들 수 있음.
문제는 foreach 문을 통해 요소를 순회할 때마다 형식 변환을 수행해야 해서 성능 저하가 발생함.

결과:
IEnumerable 인터페이스, IEnumerator 인터페이스를 상속하면,
형식 변환으로 인한 성능 저하 없이 foreach 순회가 가능한 클래스를 작성할 수 있음.

IEnumerable 인터페이스의 메소드?
(1) IEnumerator GetEnumerator(): IEnumerator 형식의 객체를 반환(IEnumerable로부터 상속받은 메소드임)
(2) IEnumerator GetEnumerator(): IEnumerator 형식의 객체를 반환(새로 선언)

IEnumerator 인터페이스의 메소드와 프로퍼티?
(1) bloolean MoveNext()
(2) void Reset()
(3) Object Current{get;}: 컬렉션의 현재 요소를 반환(IEnumerator로부터 상속받은 프로퍼티임)
(4) T Current{get;}: 컬렉션의 현재 요소를 반환(새로 선언)
*/

// MyList라는 클래스를 정의하고,
// 이 클래스가 IEnumerable와 IEnumerator 인터페이스를 구현하여
// foreach 문에서 사용할 수 있도록 하는 코드.
// (IEnumerable과 IEnumerator 인터페이스를 구현하여,
// 제네릭 클래스를 foreach 문에서 사용할 수 있도록 하는 방법을 보여줍니다.)
namespace EnumerableGeneric
{
class MyList : IEnumerable, IEnumerator
{
private T[] array; // T 타입의 배열을 저장할 array 필드를 선언
int position = -1; // 현재 요소의 인덱스를 저장할 position 필드를 선언하고,
// -1로 초기화합니다.
// position은 현재 배열 요소의 위치를 나타내는 필드입니다.
// -1은 초기 상태를 의미합니다.
// 왜 0이 아니라 -1로 초기화 할까?
// MoveNext() 메서드를 보면, 처음 호출될 때 position 값을 1 증가시키고
// 배열의 0번째 요소부터 접근하게 됩니다.
// 만약 position을 0으로 초기화한다면, MoveNext() 메서드를 처음 호출했을 때
// position이 1이 되어 배열의 1번째 요소부터 접근하게 되버림.
// 따라서 배열의 0번째 요소부터 순회하기 위해 position을 -1로 초기화하는 것입니다.

    public MyList() // MyList<T> 클래스의 생성자입니다. 
                    // 객체가 생성될 때 호출되며,
                    // array 필드를 초기 크기가 3인 배열로 초기화합니다.
    {
        array = new T[3];
    }


    public T this[int index] // 인덱서를 정의합니다. 
    {
        get // get 접근자는 array[index]를 통해, 인덱스에 해당하는 배열 요소를 반환하고, 
        {
            return array[index];
        }

        set // set 접근자는 array[index] = value를 통해, 인덱스에 해당하는 배열 요소에 값을 할당합니다.
        {
            if (index >= array.Length) // index가 배열의 크기를 벗어나면,
            {
                Array.Resize<T>(ref array, index + 1); // 배열의 크기를 index + 1로 조정합니다.
                Console.WriteLine($"Array Resized : {array.Length}");
            }

            array[index] = value;
        }
    }


    public int Length // array 필드의 길이를 반환하는 프로퍼티인 Length를 선언.
                      // MyList<T> 클래스에서 array 필드의 길이를 반환하는 프로퍼티를 선언하는 이유?
                      // MyList<T> 객체를 사용하는 외부 코드에서 
                      // 배열의 길이를 알 수 있도록 하기 위해서입니다.
                      // array 필드는 private 접근 제한자를 가지므로, 
                      // MyList<T> 클래스 외부에서는 직접 접근할 수 없습니다. 
                      // 따라서 Length 프로퍼티를 통해 array 필드의 길이를 간접적으로 제공하는 것입니다.
                      // Length 프로퍼티는 get 접근자만 가지고 있으므로 읽기 전용입니다. 
                      // 외부 코드에서 Length 프로퍼티를 통해 배열의 길이를 읽을 수는 있지만,
                      // 값을 변경할 수는 없습니다.
    {
        get { return array.Length; }
    }


    public IEnumerator<T> GetEnumerator() // IEnumerator<T> 인터페이스를 구현하는
                                          // GetEnumerator() 메서드를 정의합니다. 
                                          // 이 메서드는 현재 객체(this)를 반환합니다.
    {
        return this;
    }


    IEnumerator IEnumerable.GetEnumerator() // IEnumerable 인터페이스를 구현하는
                                            // GetEnumerator() 메서드를 정의합니다.
                                            // 이 메서드는 현재 객체(this)를 반환합니다.
    {
        return this;
    }


    public T Current // 현재 요소를 반환하는 프로퍼티입니다.
                     // IEnumerator<T> 인터페이스의 멤버입니다.
    {
        get { return array[position]; }
    }


    object IEnumerator.Current // 현재 요소를 반환하는 프로퍼티입니다.
                               // IEnumerator 인터페이스의 멤버입니다.
    {
        get { return array[position]; }
    }


    public bool MoveNext() // 다음 요소로 이동하는 메서드입니다. 
                           // IEnumerator<T> 인터페이스의 멤버입니다.
    {
        if (position == array.Length - 1)
        {
            Reset();
            return false;
        }

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


    public void Reset() // 인덱스를 초기화하는 메서드입니다. 
                        // IEnumerator<T> 인터페이스의 멤버입니다.
    {
        position = -1; ;
    }


    public void Dispose() // 리소스를 해제하는 메서드입니다.
                          // IEnumerator<T> 인터페이스의 멤버입니다.
    {

    }
}


class MainApp
{
    static void Main(string[] args)
    {
        MyList<string> str_list = new MyList<string>(); // 문자열 타입의 MyList<string> 객체
                                                        // str_list를 생성합니다.
        str_list[0] = "abc"; // 인덱서를 사용하여 str_list 객체에 문자열 값을 할당합니다. 
                             //  index가 배열 크기를 벗어나면 배열의 크기가 자동으로 조정됩니다.
        str_list[1] = "def";
        str_list[2] = "ghi";
        str_list[3] = "jkl";
        str_list[4] = "mno";

        foreach (string str in str_list) // foreach 문을 사용하여 str_list 객체의 모든 요소를 출력합니다.
            Console.WriteLine(str);

        Console.WriteLine();

        MyList<int> int_list = new MyList<int>(); // 정수 타입의 MyList<int> 객체
                                                  // int_list를 생성합니다.
        int_list[0] = 0; // 인덱서를 사용하여 int_list 객체에 정수 값을 할당합니다.
                         // 인덱스가 배열 크기를 벗어나면 배열의 크기가 자동으로 조정됩니다.
        int_list[1] = 1;
        int_list[2] = 2;
        int_list[3] = 3;
        int_list[4] = 4;

        foreach (int no in int_list) // foreach 문을 사용하여 int_list 객체의 모든 요소를 출력합니다.
            Console.WriteLine(no);
    }
}

}

/*
출력 결과

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

Array Resized : 4
Array Resized : 5
0
1
2
3
4
*/

0개의 댓글