[C#] 5.1 캡슐화 대상(5.1.4 인덱서)

인덱서는 멤버 요소들로 구성된 컬렉션 개체의 요소에 쉽게 접근할 수 있게 해 주는 멤버입니다. 인덱서는 매개 변수가 있다는 점을 제외하면 구현 방법이 속성과 매우 흡사하며 속성처럼 get 블록과 set 블록을 선택적으로 정의할 수 있습니다.

인덱스를 캡슐화할 때에는 요소 형식, this 키워드, [매개 변수]를 선언하고 내부에 get 혹은 set 블록을 정의하면 됩니다.

class Example
{
    ...
    public string this[int index]
    {
        get
        {
            return ...;
        }
        set
        {
            ...[index] = value;
        }
    }
}

사용하는 곳에서는 인덱스 연산자를 이용하여 원하는 요소를 참조할 수 있습니다.

static void Main(string[] args)
{
    Example ex = new Example();
    ....
    ex[3] = "홍길동";
    Console.WriteLine("{0}",ex[3]);
}

간단한 코드를 통해 인덱서에 대해 살펴봅시다. 여기에서는 키와 값을 쌍으로 하는 문자열이 보관된 사전에서 키를 입력하면 원하는 값을 찾아주는 예를 들려고 합니다.

문자열을 요소로 갖는 MyDictionary 클래스를 정의합시다. 여기에서는 요소 문자열을 3개를 갖게 정의할게요.

class MyDictionary
{
    string[] storage = new string[3];
}

MyDictionary 클래스는 요소 문자열로 “key0=value0”, “key1=value1”, “key2=value2″을 보관할게요.

public MyDictionary()
{
    for (int i = 0; i < storage.Length; i++)
    {
        storage[i] = string.Format("key{0}=value{0}",i,i);
    }
}

MyDictionary는 보관된 요소를 정수를 인자로 받는 인덱서와 문자열을 인자로 받는 인덱서를 제공합시다.

public string this[int index]
{
    get;
}
public string this[string key]
{
    get;
}

정수를 인자로 받는 인덱서는 요소를 보관하는 storage의 유효한 범위에 있는 인자가 오면 해당 요소의 값을 반환하게 정의합시다.

public string this[int index]
{
    get
    {
        if (AvailIndex(index)) //유효한 인덱스일 때
        {
            return GetValue(storage[index]); //storage[index] 요소의 값을 반환
        }
        return string.Empty;
    }
}

MyDictionary에 요소 문자열을 storage에 보관하므로 유효한 인덱스는 0보다 크거나 같으면서 stroage의 Length보다 작아야 합니다.

private bool AvailIndex(int index)
{
    return (index >= 0) && (index < storage.Length);
}

요소 문자열은 “키=값”형태로 보관되어 있으므로 ‘=’의 위치를 찾아 뒤쪽에 있는 부분 문자열이 값입니다.

private string GetValue(string s)
{
    int index = s.Index Of('='); //'='문자가 시작되는 위치를 찾는다.
    return s.Substring(index+1); //index+1 뒤에 있는 부분 문자열을 반환한다.
}

문자열을 인자로 받는 인덱서는 인자로 전달된 키에 해당하는 요소를 찾은 후에 요소 문자열에서 값을 추출하여 반환해야 합니다.

public string this[string key]
{
    get
    {
        string element =FindKey(key); //키에 해당하는 요소 문자열을 찾는다.
       return GetValue(element); //요소 문자열에서 값을 추출하여 반환한다.
    }
}

키를 인자로 받아 요소를 찾는 메서드에서는 요소 문자열이 보관된 storage의 각 요소중에 시작 부분이 인자로 받은 키인 요소를 반환하면 되겠죠.

private string FindKey(string key)
{
    foreach (string s in storage) //storage 컬렉션에 보관된 각 요소에 대해 반복 수행
    {
        if (s.IndexOf(key) == 0) //요소 문자열이 key로 시작할 때
        {
            return s; //요소 문자열 반환
        }
    }
    return string.Empty; 
}

이처럼 인덱서를 제공하면 사용하는 곳에서는 인덱스를 통해 원하는 요소의 값을 얻어올 수 있게 됩니다.

MyDictionary md = new MyDictionary();
Console.WriteLine(md[0]); //정수를 인자로 받는 인덱서 사용
Console.WriteLine(md["key2"]); //문자열을 인자로 받는 인덱스 사용

▶ 인덱서 캡슐화 예제 코드

using System;

namespace Ex_Indexers
{
    class MyDictionary
    {
        string[] storage = new string[3];

        public MyDictionary()
        {
            for (int i = 0; i < storage.Length; i++)
            {
                storage[i] = string.Format("key{0}=value{0}",i,i);
            }
        }

        public string this[int index] //정수를 인자로 받는 인덱서
        {
            get
            {
                if (AvailIndex(index)) //유효한 인덱스일 때
                {
                    return GetValue(storage[index]); //storage[index] 요소의 값을 반환
                }
                return string.Empty;
            }
        }



        public string this[string key] //문자열을 인자로 받는 인덱서
        {
            get
            {
                string element =FindKey(key); //키에 해당하는 요소 문자열을 찾는다.
                return GetValue(element); //요소 문자열에서 값을 추출하여 반환한다.
            }
        }
        
        private bool AvailIndex(int index)
        {
            return (index >= 0) && (index < storage.Length);
        }

        private string FindKey(string key)
        {
            foreach (string s in storage) //보관된 각 요소에 대해 반복 수행
            {
                if (s.IndexOf(key) == 0) //요소 문자열이 key로 시작할 때
                {
                    return s; //요소 문자열 반환
                }
            }
            return string.Empty;
        }

        private string GetValue(string s)
        {
            int index = s.IndexOf('='); //'='문자가 시작되는 위치를 찾는다.
            return s.Substring(index+1); //index+1 뒤에 있는 부분 문자열을 반환한다.
        }
        public int Size //요소를 보관하는 storage의 크기를 반환
        {
            get
            {
                return storage.Length;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyDictionary md = new MyDictionary();
            for (int i = 0; i < md.Size; i++)
            {
                Console.WriteLine(md[i]); //정수를 인자로 받는 인덱서 사용
            }
            Console.WriteLine("____________________");
            Console.WriteLine(md["key2"]); //문자열 인자로 받는 인덱서 사용
        }
    }
}

▶ 실행 결과

value0
value1
value2
________________________
value2