상세 컨텐츠

본문 제목

[C#] 배열, 컬렉션, 인덱서

.NET/C#

by 클리엘 클리엘 2021. 10. 13. 00:48

본문

728x90

1. 배열

 

배열의 기본적인 개념은 '같은 성격의 데이터를 여러 개 모아놓은 것'이라고 볼 수 있습니다. 예를 들어 어떤 학급의 학생별 시험 점수를 처리하기 위해 다음과 같이 변수를 선언했다면

class Program
{
    static void Main(string[] args)
    {
        int 홍길송 = 70;
        int 홍길남 = 80;
        int 홍길순 = 50;
        int 홍길석 = 100;
        int 홍길병 = 70;
    }
}

이를 배열로는 대괄호를 사용해 아래와 같이 선언할 수 있습니다.

class Program
{
    static void Main(string[] args)
    {
        int[] score = new int[5];
        score[0] = 70;
        score[1] = 80;
        score[2] = 50;
        score[3] = 100;
        score[4] = 70;
    }
}

대괄호 안에 숫자를 넣어서 배열을 선언하면 내부적으로 해당 데이터형식의 변수가 배열로 지정한 숫자만큼 생성됩니다. 이때 배열은 0부터 시작하므로 5개의 배열은 0부터 4까지의 인덱스라는 숫자를 가질 수 있으며 인덱스를 통해 각각의 배열 요소에 접근해 값을 설정하거나 설정된 값을 가져올 수 있게 됩니다.

 

예제에서 배열은 new int[5]를 통해 배열의 크기만을 지정해 생성하고 이후에 배열에 값을 할당했지만 다음과 같이 중괄호로 배열의 값을 지정하는 컬렉션 초기자를 사용해 줄 수도 있습니다. 배열을 생성함과 동시에 배열 값을 초기화하는 것입니다.

int[] score = new int[5] { 70, 80, 50, 100, 70 };

또한 컬렉션 초기자를 사용하는 경우 내부의 초기화 값을 통해서 배열의 크기를 유추할 수 있으므로 배열크기지정은 생략할 수 있습니다.

int[] score = new int[] { 70, 80, 50, 100, 70 };

사실 아주 간편한 방법도 있는데 new와 배열의 형식및 길이를 모두 생략하는 것입니다.

int[] score = { 70, 80, 50, 100, 70 };

배열을 생성하고 나면 가장 처음의 예제에서 처럼 배열의 인덱스를 통해 배열의 각 요소에 접근할 수 있지만 for나 while문을 통해 배열을 순회하면 좀 더 편리하게 배열을 활용할 수 있습니다.

class Program
{
    static void Main(string[] args)
    {
        int[] score = new int[5];
        score[0] = 70;
        score[1] = 80;
        score[2] = 50;
        score[3] = 100;
        score[4] = 70;

        int sum = 0;
        foreach(int i in score) {
            sum += i;
        }

        WriteLine($"점수 합계 : {sum}");
    }
}

배열에 인덱스를 지정할때 배열은 0부터 시작하므로 마지막 배열은 전체 배열 길이에 -1을한 값이 됩니다. 따라서 예제의 score배열의 전체 길이는 5이고 여기서 -1을 하면 4가 되므로

class Program
{
    static void Main(string[] args)
    {
        int[] score = new int[5];
        score[0] = 70;
        score[1] = 80;
        score[2] = 50;
        score[3] = 100;
        score[4] = 70;

        WriteLine($"마지막 배열 값 : {score[4]}");
    }
}

인덱스로 4를 지정하면 마지막 배열값을 가져올 수 있게 됩니다. 하지만 대부분의 경우 값을 명시적으로 사용하기보다 배열의 길이에서 -1을 하여 마지막 값을 산출하는 경우가 더 많습니다.

WriteLine($"마지막 배열 값 : {score[score.Length - 1]}");

배열의 길이는 length속성으로 구할 수 있고 여기에서 -1을 하면 자연스럽게 마지막 배열값을 가져올 수 있습니다. 그런데 C# 8.0부터는 인덱스를 지정할 때 ^ 연산자를 사용할 수 있게 됐습니다. ^ 연산자가 하는 역할은 배열의 마지막을 지정하는 것으로 ^1이라고 하면 마지막에서 첫 번째 인덱스, ^2라고 하면 마지막에서 두 번째 인덱스를 가리키는 것이 됩니다.

WriteLine($"마지막 배열에서 첫번째 값 : {score[^1]}"); //70
WriteLine($"마지막 배열에서 두번째 값 : {score[^2]}"); //100

참고로 마지막 배열을 지정하는 인덱스를 특정 변수에 저장하려면 System.Index 형식을 사용합니다.

System.Index last = ^1;

배열은 System.Array클래스에 기반하므로 배열을 사용하게 되면 length속성으로 배열의 길이를 확인하거나 indexOf() 메서드등으로 배열의 위치 등을 확인하는 System.Array클래스에 구현된 여러 가지 메서드와 속성을 사용할 수 있습니다.

class Program
{
    static void Main(string[] args)
    {
        int[] score = { 70, 80, 50, 100, 70 };

        WriteLine(score.GetType().BaseType); //int[] -> System.Array
    }
}

System.Array에서는 방금 언급했던 length속성이나 indexOf()메서드를 가지고 있으므로 이 클래스에서 파생된 개체도 당연히 해당 속성이나 메서드를 사용할 수 있게 되는 것입니다.

class Program
{
    static void Main(string[] args)
    {
        int[] score = { 70, 80, 50, 100, 70 };

        //배열 index 검색
        WriteLine(Array.IndexOf(score, 50)); //2
    }
}

우선 배열의 index검색은 IndexOf 메서드를 사용합니다. 첫번째 매개변수는 배열 자체를 지정하고, 두 번째 매개변수는 배열에서 검색할 요소의 값을 지정합니다. 그러면 해당 값이 위치한 배열의 index를 반환하게 됩니다.

class Program
{
    static void Main(string[] args)
    {
        int[] score = { 70, 80, 50, 100, 70 };

        Array.Sort(score); //배열 정렬

        void array(int item)
        {
            WriteLine(item);
        }

        Array.ForEach<int>(score, new Action<int>(array)); //5, 70, 70, 80, 100
    }
}

배열을 정렬할때는 Sort() 메서드를 사용합니다. Sort() 메서드의 매개변수로는 배열 자체를 넘겨주면 됩니다. 참고로 정렬 이후에 값을 확인할 때는 ForEach() 메서드를 사용합니다. ForEach다음에 <와 > 사이에 배열의 데이터 형식을 지정하고 첫 번째 매개변수에 배열을 넘겨주면 됩니다. 두 번째 매개변수는 첫 번째 매개변수에서 지정한 배열에서 각 요소 값이 반환될 때 이를 어떻게 처리할지 지정하는 Action <T> 형식을 넘겨줍니다. 예제에서는 array라는 임의의 로컬 함수를 만들어 넘어오는 값을 출력하도록 하였고 해당 메서드를 Action<T>클래스의 개체를 생성할 때 생성자 매개변수로 전달하였습니다.

class Program
{
    static void Main(string[] args)
    {
        int[] score = { 70, 80, 50, 100, 70 };

        if (Array.TrueForAll(score, item => { return item >= 90 ? true : false; }))
            WriteLine("전체합격");
        else
            WriteLine("탈락자발생");
    }
}

TrueForAll은 요소의 값이 지정한 조건에 부합하는지를 확인하는 메서드입니다. 위의 예제와 동작이 비슷하긴 하지만 메서드 부분에서 익명메서드를 활용하였습니다.

 

TureForAll메서드는 첫번째 매개변수로 배열을 두 번째 매개변수로 실제 요소 값을 확인하고 그 결과를 bool형식으로 반환하는 일치자를 지정합니다. 일치자는 Predicate<T>형식인데 로컬 함수와 같이 따로 분리하는 것이 아니라 익명 메서드를 통해 직접 일치자를 구현했다는 차이가 있습니다.

class Program
{
    static void Main(string[] args)
    {
        int[] score = { 70, 80, 50, 100, 70 };

        Array.Sort(score);
        int index = Array.BinarySearch(score, 80); //3

        WriteLine(index);
    }
}

BinarySearch() 메서드는 '이진탐색'알고리즘으로 원하는 데이터를 검색하는 방법입니다. 첫 번째 매개변수에 배열을 두 번째 매개변수에 검색하고자 하는 값을 지정합니다. 순차 탐색보다 성능이 좋지만 제대로 사용하려면 배열이 우선 정렬되어 있어야 합니다.

class Program
{
    static void Main(string[] args)
    {
        int[] score = { 70, 80, 50, 100, 70 };

        WriteLine(Array.FindIndex(score, x => { return x == 100; }));
    }
}

특정 조건에 따라 검색을 수행하려면 FindIndex() 메서드를 사용합니다. 예제에서는 100의 값을 찾아 인덱스를 반환하도록 익명메서드로 검색조건을 지정하였습니다.

class Program
{
    static void Main(string[] args)
    {
        int[] score = { 70, 80, 50, 100, 70 };

        Array.Resize(ref score, 6);

        score[5] = 60;

        WriteLine(score[5]);
    }
}

Resize() 메서드는 배열의 크기를 조정할 수 있도록 합니다. 예제는 배열의크기를 6으로 하였고 이에 따라 기존에 없던 index값 5로 접근에 해당 위치의 요소에 값을 설정하고 설정한 값을 확인하고 있습니다.

class Program
{
    static void Main(string[] args)
    {
        int[] score = { 70, 80, 50, 100, 70 };

        Array.Clear(score, 0, score.Length);

        WriteLine(score[4]);
    }
}

Clear()는 배열의 각 요소값을 초기화합니다. 첫 번째 매개변수는 초기화할 배열을, 두 번째는 시작 인덱스, 세 번째 매개변수는 시작 인덱스부터 시작해 몇 개의 요소를 초기화할지 지정합니다. 값은 정수형이라면 0으로 참조형 이라면 null로 논리형이면 false로 초기화됩니다.

class Program
{
    static void Main(string[] args)
    {
        int[] score = { 70, 80, 50, 100, 70 };
        int[] score2 = new int[5];

        Array.Copy(score, score2, score.Length);

        WriteLine(score2[4]);
    }
}

Copy() 메서드는 배열을 복사합니다. 첫 번째와 두 번째 매개변수에 각각 복사할 원본 배열과 복사 대상 배열을 지정하며 복사할 길이 값을 마지막으로 지정합니다. 

class Program
{
    static void Main(string[] args)
    {
        int[] score = { 70, 80, 50, 100, 70 };
        int[] score2 = score[0..2];

        WriteLine(score2[1]);
    }
}

길이값 대신 [0.. 2]처럼 복사할 시작 인덱스와 길이를 지정해 줄 수도 있습니다. 예제에서는 0..2로 범위를 할당하였으므로 0부터 2개의 요소만을 복사하게 됩니다. 범위는 ..에서 앞뒤 숫자를 생략할 수도 있는데 이런 경우 범위 전체를 복사 대상으로 간주하게 되며 앞에 숫자만 생략하는 경우 0부터, 뒤의 숫자만을 생략하는 경우 지정한 인덱스부터 끝까지를 복사대상으로 함을 의미하게 됩니다.

class Program
{
    static void Main(string[] args)
    {
        int[] score = { 70, 80, 50, 100, 70 };

        System.Range r = ..;

        int[] score2 = score[r];

        WriteLine(score2[4]);
    }
}

참고로 범위 값을 변수에 담으려면 Range형식의 범위 변수를 사용해야 합니다.

 

이 외에 배열이 다차원인 경우 차원의 수를 반환하는 GetLength() 메서드나 배열의 길이를 나타내는 Length, 순위를 나타내는 Rank속성 등이 존재합니다.

 

2. 다차원 배열

 

배열 안에 또 다른 배열이 들어가는 형태를 다차원 배열이라고 합니다. 다차원 배열은 다음과 같이 선언하고 사용할 수 있습니다.

class Program
{
    static void Main(string[] args)
    {
        int[,] score = { { 10, 20 }, { 30, 40 }, { 50, 60 } };

        WriteLine(score[0, 1]); //20
    }
}

각 차원은 [와 ] 사이에 콤마(,) 문자로 구분하여 선언됩니다. 만약 배열을 먼저 선언하고 나중에 값을 초기화해야 하는 경우라면 아래와 같은 방법을 사용할 수도 있습니다.

class Program
{
    static void Main(string[] args)
    {
        int[,] score = new int[3,2];

        score[0, 0] = 10;
        score[0, 1] = 20;

        score[1, 0] = 30;
        score[1, 1] = 40;

        score[2, 0] = 50;
        score[2, 1] = 60;

        WriteLine(score[0, 1]); //20
    }
}

예제는 2차원이므로 값을 설정하거나 가져올 때 '[첫 번째 차원의 인덱스, 두 번째 차원의 인덱스]'의 형식으로 차원을 지정해야 합니다.

 

차원의 수는 제한이 없으므로 원하는 만큼의 차원을 [,,,,] 형태로 만들 수 있으나 너무 많은 차원은 설계를 다시 고려하는 것이 좋습니다.

 

3. 가변 배열

 

다차원 배열과 비슷하게 보이지만 전혀 다른 형태의 배열입니다. 가변 배열은 먼저 선언한 배열에서 각각의 요소에 또 다른 배열을 선언하는 형태인데 일반 배열에서는 요소에 단순히 값을 저장했다면 가변 배열에서는 요소에 또 다른 배열을 선언하는 형태라고 보시면 됩니다.

 

가변 배열은 다음과 같이 선언합니다.

class Program
{
    static void Main(string[] args)
    {
        int[][] score = new int[3][];

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

예제에서는 우선 길이가 3인 배열을 선언하고 첫 번째 요소에 길이가 2개인 배열을, 두 번째 요소에서는 길이가 3개인 배열을 선언하되 값을 초기화하고 있고, 마지막 요소에서는 길이가 4개인 배열을 선언하고 있습니다. 여러 개의 배열을 다룰 수 있다는 것만 빼면 사실상 일반 배열과 크게 다르지 않습니다.

class Program
{
    static void Main(string[] args)
    {
        int[][] score = new int[3][];

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

        score[0][0] = 10;

        WriteLine(score[0][0]);
    }
}

값을 담거나 가져올 때도 첫 번째 배열의 요소를 지정한다는 것만 빼면 일반 배열과 동일하게 사용됩니다.

 

4. 컬렉션

 

컬렉션도 배열처럼 특정 데이터형의 데이터가 모여있는 구조입니다. 하지만 배열은 데이터의 길이가 고정적이었지만 컬렉션은 동적으로 데이터를 다룰 수 있다는 점에서 차이가 존재합니다.

 

● ArrayList

 

ArrayList는 다음과 같은 방법으로 선언되고 사용됩니다.

class Program
{
    static void Main(string[] args)
    {
        ArrayList myList = new ArrayList();
        myList.Add(10);
        myList.Add(20);

        myList.RemoveAt(0);

        myList.Insert(1, "30입니다.");

        WriteLine(myList[1]); //30입니다.
    }
}

ArrayList는 선언 시 배열처럼 필요한 길이를 지정하지 않습니다. 그런데 배열과 비슷하게 값을 초기화하는 것은 가능합니다. 이러한 방법은 아래에서 설명할 Queue나 Stack에서도 동일하게 적용이 가능한 방법입니다.

class Program
{
    static void Main(string[] args)
    {
        ArrayList myList = new ArrayList() { 10, 20, 30 };

        WriteLine(myList[1]); //20
    }
}

또한 ArrayList에서는 컬렉션 초기자를 사용해 값을 초기화할 수도 있는데 다만 이 방법은 IEnumerable 인터페이스를 상속받고 Add() 메서드를 구현하는 경우에만 가능하기에 그렇지 않은 Queue와 Stack에서는 사용할 수 없습니다.

class Program
{
    static void Main(string[] args)
    {
        ArrayList myList = new ArrayList(new int[] { 10, 20, 30} );

        WriteLine(myList[1]); //20
    }
}

요소의 추가는 Add()메서드를, 요소의 삭제는 RemoveAt()메소드로 배열의 인덱스를 지정해 삭제합니다. 중간에 요소를 끼워넣을 수도 있는데 Insert() 메소드를 사용하여 끼워 넣을 위치의 인덱스와 값을 지정하면 됩니다.

 

일반 배열과 비교해 또 하나 큰 차이는 정수나 문자열등 특정 데이터형을 가리지 않고 다양한 데이터를 동시에 담아낼 수 있다는 것입니다. 데이터를 추가하는 Add()나 Insert() 메서드가 이미 데이터를 object형으로 받을 수 있게끔 만들어졌기 때문인데 ArrayList에 들어가는 모든 데이터가 object형식으로 취급된다는 것을 의미합니다.

 

데이터를 object형으로 받아들이게 되면 예컨대 정수형의 데이터를 저장하면 값을 object형식으로 바꾸는 '박싱'을 수행하게 되고 다시 값을 가져오는 경우에는 object를 정수형으로 바꾸는 '언박싱'이 수행됩니다. 이는 성능에 부담을 주는 작업이 될 수 있기 때문에 사용할 때 성능에 민감한 경우라면 주의해야 합니다. 또한 이러한 문제는 ArrayList뿐만 아니라 아래에서 설명할 Stack, Queue, HashTable도 같은 문제를 안고 있습니다.

 

● Queue

 

Queue는 데이터를 뒤에서 넣고 앞에서 빼는 구조를 가지고 있습니다.

class Program
{
    static void Main(string[] args)
    {
        Queue q = new Queue();
        q.Enqueue(1);
        q.Enqueue(2);
        q.Enqueue(3);

        WriteLine(q.Dequeue());//1
        WriteLine(q.Dequeue());//2
        WriteLine(q.Dequeue());//3
    }
}

간단한 구조이므로 복잡할 것 없이 Enqueue()와 Dequeue() 메서드 정도만 알고 있으면 충분합니다. 메서드의 이름에서 처럼 Enqueue()는 값을 뒤에서 추가하며 Dequeue()는 가장 먼저 들어온 데이터를 맨 앞의 순서대로 끄집어냅니다. 또한  Dequeue() 메서드로 데이터를 한번 꺼내게 되면 해당 데이터는 Queue객체에서 완전히 사라지게 됩니다.

 

● Stack

 

Queue와 정반대의 동작을 수행합니다. 먼저 들어온 데이터가 가장 나중에 나가는 구조인데 데이터를 뒤에서 넣고 꺼낼 때는 맨뒤에 있는 데이터부터 먼저 꺼내는 구조입니다.

class Program
{
    static void Main(string[] args)
    {
        Stack s = new Stack();
        s.Push(1);
        s.Push(2);
        s.Push(3);

        WriteLine(s.Pop()); //3
        WriteLine(s.Pop()); //2
        WriteLine(s.Pop()); //3
    }
}

Stack에서 데이터 추가는 Push() 메서드를, 데이터 추출은 Pop() 메소드를 사용합니다.

 

참고로 먼저 들어온 데이터가 나중에 나가는 것을 Fist In - Last Out(선입 후출)이라고 하며 나중에 들어온 데이터가 먼저 나가는 것을 Last In - First Out(후입 선출)이라고 합니다.

 

● HashTable

 

사전처럼 키와 값을 가지고 있는 구조입니다. 간단한 자료를 저장하고 꺼내 쓰는 용도로 종종 사용되는 자료구조인데 순차 검색이 아닌 키를 통해 값을 바로 찾는 구조이므로 검색 속도가 매우 빠르다는 장점을 가지고 있습니다.

class Program
{
    static void Main(string[] args)
    {
        Hashtable ht = new Hashtable();
        ht[0] = "abc";
        ht["abc"] = 1;

        WriteLine(ht["abc"]);
    }
}

HashTable은 [와 ] 사이에 '키'를 지정하고 그 상태에서 필요한 값을 대입하면 자동으로 키와 값을 쌍으로 데이터를 저장합니다. 예제에서는 0 키에 "abc" 문자열을 저장하고 다시 "abc"키에 1이라는 값을 저장하였습니다. 그리고 데이터를 가져올 때는 키만 지정하면 해당 키에서 저장한 값을 가져오게 됩니다.

 

HashTable은 컬렉션 초기자와 비슷하게 값을 초기화할 수 있습니다. 다만 자료 구조 특성상 키와 값이 함께 동반되어야 합니다.

class Program
{
    static void Main(string[] args)
    {
        Hashtable ht = new Hashtable() {
            {0, "ABC"},
            {"A", 123},
            {10.10, "십점십"}
        };
    }
}

또한 컬렉션 초기자를 사용하지 않더라도 HashTable전용의 딕셔너리 초기자를 사용할 수도 있습니다.

class Program
{
    static void Main(string[] args)
    {
        Hashtable ht = new Hashtable() {
            [0] = "abc",
            ["a"] = 123,
            [10.10] = "십점십"
        };
    }
}

5. 인덱서

 

객체를 마치 배열처럼 사용하게 해주는 속성을 말합니다. 인덱서는 다음과 같이 선언할 수 있습니다.

class Student
{
    private string[] students;

    public Student()
    {
        students = new string[3];
    }

    public string this[int idx]
    {
        get
        {
            return students[idx];
        }

        set
        {
            students[idx] = value;
        }
    }
}

예제에서는 Student라는 클래스에서 인덱서 구현한 것입니다. 일반적인 프로퍼티를 선언할 때와 비슷한데 차이점이라면 이름 대신 this키워드가 사용됐다는 것입니다. Student클래스는 객체가 생성될 때 내부적으로 students라는 배열을 초기화하며 지정된 인덱스(idx)값에 따라 배열에서 해당 요소의 값을 가져오거나 저장할 수 있게 하는 구조입니다.

 

위와 같이 클래스에 인덱서를 구현하고 나면 클래스의 객체에서 배열처럼 요소의 첨자를 통해 특정 데이터를 저장하거나 저장된 값을 가져올 수 있습니다.

class Program
{
    static void Main(string[] args)
    {
        Student s = new Student();
        s[0] = "홍길동";
        s[1] = "홍길순";
        s[2] = "홍길석";

        WriteLine(s[1]);
    }
}

예제에서처럼 사용하게 되면 배열에서도 그랬듯이 foreach문을 통해 순회하면 될 것 같지만 사실 이것만으로 배열을 순회시키는 것은 불가능합니다. 배열의 배열 순회가 가능하려면 IEnumerable 인터페이스를 상속하고 GetEnumerator() 메서드를 구현해야 합니다.

class Student
{
    private string[] students;

    public Student()
    {
        students = new string[3];
    }

    public string this[int idx]
    {
        get
        {
            return students[idx];
        }

        set
        {
            students[idx] = value;
        }
    }

    public IEnumerator GetEnumerator()
    {
        foreach(string s in students) {
            yield return s;
        }
    }
}

GetEnumerator() 메서드는 반환형 식이 IEnumerator입니다. 이 말은 메서드가 IEnumerator 형식의 객체를 반환해야 한다는 것을 의미합니다. IEnumerator 인터페이스는 배열 순회에 필요한 MoveNext()나 Reset() 메서드 그리고 Current 속성 등을 정의하고 있는데 yield return을 사용해 배열 값을 반환하는 GetEnumerator() 메서드를 직접 구현하면 컴파일러가 알아서 IEnumerator 인터페이스를 상속한 클래스를 생성해줍니다. 별도로 위에서 언급한 메서드를 구현해주지 않아도 되는 것입니다.

 

yield return은 지속적인 배열 값의 반환을 위한 것입니다. return만 사용해 값을 반환하게 되면 처음 값을 하나만 반환하고 그 이후에는 그대로 동작이 멈춰버리는 문제가 생기므로 모든 값을 지속적으로 반환시키기 위해 yeild return은 필수적입니다. (yield break를 사용하는 경우도 있는데 이는 return처럼 메서드를 종료시키는 역할을 합니다.)

 

클래스에서 GetEnumerator() 메서드를 구현하고 나면 다음과 같이 foreach문을 통해서 배열 순회가 가능해집니다.

class Program
{
    static void Main(string[] args)
    {
        Student s = new Student();
        s[0] = "홍길동";
        s[1] = "홍길순";
        s[2] = "홍길석";

        foreach(string sd in s)
        {
            WriteLine(sd);
        }
    }
}

보시는 바와 같이 비교적 수월하게 인덱서를 사용해 열거 가능한 클래스를 구현하였습니다. 하지만 필요하다면 직접 IEnumerator와 IEnumerable 인터페이스를 상속해 필요한 메서드와 속성을 구현해 줄 수도 있습니다.

class Student : IEnumerator
{
    private string[] students;
    private int position = -1; //현재 배열 위치값

    public Student()
    {
        students = new string[3];
    }

    public string this[int idx]
    {
        get
        {
            return students[idx];
        }

        set
        {
            students[idx] = value;
        }
    }

    //IEnumerator 인터페이스 구현 - 현재 요소값 변환
    public object Current
    {
        get {
            return students[position];
        }
    }

    //IEnumerator 인터페이스 구현 - 다음 요소로 이동
    public bool MoveNext()
    {
        if (position >= students.Length - 1) {
            Reset();
            return false;
        }
        else {
            ++position;

            return (position < students.Length);
        }
    }

    //IEnumerator 인터페이스 구현 - 요소 위치 초기화
    public void Reset()
    {
        position = -1;
    }
}

먼저 IEnumerator 인터페이스를 상속받아 해당 인터페이스에 정의된 Current와 MoveNext(), Reset() 메서드를 구현합니다. Current 속성은 현재 요소의 값을 반환하며 MoveNext() 메서드는 배열의 다음 요소로 위치를 이동하도록 합니다. 마지막으로 Reset()은 요소의 위치 값을 초기화시켜주는 메서드입니다.

class Student : IEnumerator, IEnumerable
{
    private string[] students;
    private int position = -1; //현재 배열 위치값

    public Student()
    {
        students = new string[3];
    }

    public string this[int idx]
    {
        get
        {
            return students[idx];
        }

        set
        {
            students[idx] = value;
        }
    }

    //IEnumerator 인터페이스 구현 - 현재 요소값 변환
    public object Current
    {
        get {
            return students[position];
        }
    }

    //IEnumerator 인터페이스 구현 - 다음 요소로 이동
    public bool MoveNext()
    {
        if (position >= students.Length - 1) {
            Reset();
            return false;
        }
        else {
            ++position;

            return (position < students.Length);
        }
    }

    //IEnumerator 인터페이스 구현 - 요소 위치 초기화
    public void Reset()
    {
        position = -1;
    }

    public IEnumerator GetEnumerator()
    {
        return this;
    }
}

그다음 IEnumerable 인터페이스를 상속받아 GetEnumerator() 메서드를 구현합니다. GetEnumerator() 메서드는 IEnumerator형식을 반환하는데 이미 앞에서 IEnumerator로부터 상속받아 필요한 속성과 메서드를 모두 구현했으므로 Student클래스 자체가 이미 IEnumerator형식을 따르게 되었습니다. 따라서 GetEnumerator() 메서드에서는 자기 자신을 반환하도록만 해주면 됩니다.

 

728x90

'.NET > C#' 카테고리의 다른 글

[C#] 예외처리  (0) 2021.10.15
[C#] 일반화 프로그래밍  (0) 2021.10.14
[C#] 배열, 컬렉션, 인덱서  (0) 2021.10.13
[C#] 프로퍼티(Property)  (0) 2021.10.07
[C#] 인터페이스와 추상클래스  (0) 2021.10.07
[C#] 클래스(Class)  (0) 2021.10.06

관련글 더보기

댓글 영역