Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
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 31
Archives
Today
Total
05-29 03:48
관리 메뉴

nomad-programmer

[Programming/C#] 인덱서 (Indexer) 본문

Programming/C#

[Programming/C#] 인덱서 (Indexer)

scii 2020. 9. 15. 02:53

인덱서(Indexer) 는 인덱스(Index) 를 이용해서 객체 내의 데이터에 접근하게 해주는 프로퍼티라고 생각하면 된다.
객체를 마치 배열처럼 사용할 수 있게 해준다. 인덱서를 선언하는 형식은 다음과 같다.

class 클래스이름
{
    // 인덱스의 식별자가 꼭 index라는 이름일 필요는 없다.
    한정자 인덱서형식 this[형식 index]
    {
        get
        {
            // index를 이용하여 내부 데이터 반환
        }
        
        set
        {
            // index를 이용하여 내부 데이터 저장
        }
    }
}

인덱서는 프로퍼티처럼 식별자(변수 이름) 를 따로 가지지 않는다. 프로퍼티가 이름을 통해 객체 내의 데이터에 접근하게 해준다면, 인덱서는 인덱스를 통해 객체 내의 데이터에 접근하게 해준다.

using System;
using System.Collections;

namespace test
{
    class MyList
    {
        private int[] array;

        public MyList() => array = new int[3];

        // 인덱서 정의
        public int this[int index]
        {
            get { return array[index]; }
            set
            {
                if (index >= array.Length)
                {
                    Array.Resize<int>(ref array, index + 1);
                    Console.WriteLine($"Array Resized : {array.Length}");
                }

                array[index] = value;
            }
        }

        public int Length
        {
            get { return array.Length; }
        }
    }

    internal class Program
    {
        public static void Main(string[] args)
        {
            MyList myList = new MyList();
            for (int i = 0; i < 5; i++)
            {
                // 객체를 배열 다루듯 인덱스를 통해 데이터 입력
                myList[i] = i;
            }

            for (int i = 0; i < myList.Length; i++)
            {
                // 데이터를 얻어올 때도 인덱스를 이용
                Console.WriteLine(myList[i]);
            }
        }
    }
}


/* 결과

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

*/

프로퍼티는 객체 내의 데이터에 접근할 수 있도록 하는 통로이다. 인덱서도 프로퍼티처럼 객체 내의 데이터에 접근할 수 있도록 하는 통로이다. 프로퍼티와 다른 점이라면 "인덱스(index)" 를 이용한다는 것이다.


foreach가 가능한 객체 생성

foreach문은 for문처럼 요소의 위치를 위한 인덱스 변수를 선언할 필요가 없다. 조건식이나 증감식을 쓰지 않아도 된다. 그래서 for 문을 이용한 코드에 비해 foreach 문을 이용한 코드는 쓰기에도 좋고, 읽기에도 좋다. 단, 성능은 for가 월등히 좋다.
허나 foreach 문은 아무 형식의 객체에서나 사용할 수 있는 것은 아니다. 배열이나 리스트 같은 컬렉션에서만 사용할 수 있다. 위에서 만든 MyList는 foreach문이 가능할까? 될 것 같지만 절대 되지 않는다. 
foreach 구문은 IEnumerable과 IEnumerator를 상속한느 형식만 지원하기 때문이다. 이 말은 즉, MyList 클래스도 IEnumerable과 IEnumerator를 상속하기만 하면 foreach 구문을 이용하여 요소를 순회할 수 있는 할 수 있다는 이야기다.
먼저 IEnumerable 인터페이스가 갖고 있는 메소드는 다음과 같이 단 하나뿐이다. 

메소드 설명
IEnumerator GetEnumerator() IEnumerator 형식의 객체를 반환

GetEnumerator() 메소드를 구현할 때는 yield return 문의 도움을 받아야 한다. yield return 문은 현재 메소드 (GetEnumerator())의 실행을 일시 정지시켜 놓고 호출자에게 결과를 반환한다. 메소드가 다시 호출되면, 일시 정지된 실행을 복구하여 yield return 또는 yield break 문을 만날 때까지 나머지 작업을 실행하게 된다.

yield 키워드를 이용한 간단한 예제

using System;
using System.Collections;

namespace test
{
    class MyEnumerator
    {
        private int[] numbers = {1, 2, 3, 4};

        public IEnumerator GetEnumerator()
        {
            yield return numbers[0];
            yield return numbers[1];
            yield return numbers[2];
            yield break;
            yield return numbers[3];
        }
    }

    internal class Program
    {
        public static void Main(string[] args)
        {
            var obj = new MyEnumerator();
            foreach (int i in obj)
            {
                Console.WriteLine(i);
            }
        }
    }
}


/* 결과

1
2
3

*/

GetEnumerator() 메소드는 IEnumerator 형식의 객체, 즉 IEnumerator 인터페이스를 상속하는 클래스의 객체를 반환하면 된다.
아래는 IEnumerator 인터페이스의 메소드 및 프로퍼티 목록이다.

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

IEnumerable, IEnumerator 상속받아 사용하는 예제

using System;
using System.Collections;

namespace test
{
    class MyList : IEnumerable, IEnumerator
    {
        private int[] array;
        private int position = -1;

        public MyList() => array = new int[3];

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

                array[index] = value;
            }
        }

        // IEnumerator로부터 상속받은 Current 프로퍼티.
        // 현재 위치의 요소를 반환.
        public object Current
        {
            get { return array[position]; }
        }

        // IEnumerator로부터 상속받은 Reset() 메소드.
        // 요소의 위치를 첫 요소의 "앞"으로 옮긴다.
        public void Reset()
        {
            position = -1;
        }

        // IEnumerator로부터 상속받은 MoveNext() 메소드.
        // 다음 위치의 요소로 이동한다.
        public bool MoveNext()
        {
            if (position == (array.Length - 1))
            {
                Reset();
                return false;
            }

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

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

    internal class Program
    {
        public static void Main(string[] args)
        {
            MyList list = new MyList();
            for (int i = 0; i < 5; i++)
            {
                list[i] = i;
            }

            foreach (int ele in list)
            {
                Console.WriteLine(ele);
            }
        }
    }
}


/* 결과

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

*/
Comments