C# 인덱서(Indexer) 마스터하기: 객체를 배열처럼 사용하기
인덱서(Indexer)를 사용하면 객체를 배열처럼 인덱싱할 수 있습니다. 클래스에 인덱서를 정의하면 이 클래스는 가상 배열(Virtual Array)처럼 동작합니다. 그러면 이 클래스의 인스턴스를 배열 접근 연산자([ ])로 접근할 수 있습니다.
인덱서 구문 (Syntax)
1차원 인덱서 구문:
요소_타입 this[int 인덱스]
{
// get 접근자
get
{
// 인덱스로 지정된 값 반환
}
// set 접근자
set
{
// 인덱스로 지정된 값 설정
}
}
인덱서 용도
인덱서 선언 동작은 프로퍼티와 유사하게 get, set 접근자를 사용합니다. 하지만:
- 프로퍼티: 특정 데이터 멤버 반환/설정
- 인덱서: 객체 인스턴스에서 특정 값 반환/설정 (인스턴스 데이터를 작은 부분으로 나누고 각 부분에 인덱스 부여)
프로퍼티는 이름을 가지지만, 인덱서는 this 키워드로 정의하며 객체 인스턴스를 참조합니다.
기본 인덱서 예제
using System;
namespace IndexerApplication
{
class IndexedNames
{
private string[] nameList = new string[Size];
public static int Size = 10;
public IndexedNames()
{
for (int i = 0; i < Size; i++)
nameList[i] = "N.A.";
}
// 인덱서 정의
public string this[int index]
{
get
{
string tmp;
if (index >= 0 && index <= Size - 1)
tmp = nameList[index];
else
tmp = "";
return tmp;
}
set
{
if (index >= 0 && index <= Size - 1)
nameList[index] = value;
}
}
static void Main(string[] args)
{
IndexedNames names = new IndexedNames();
// 인덱서로 값 설정 (set 접근자 호출)
names[0] = "Zara";
names[1] = "Riz";
names[2] = "Nuha";
names[3] = "Asif";
names[4] = "Davinder";
names[5] = "Sunil";
names[6] = "Rubic";
// 인덱서로 값 읽기 (get 접근자 호출)
for (int i = 0; i < IndexedNames.Size; i++)
{
Console.WriteLine(names[i]);
}
Console.ReadKey();
}
}
}
출력: Zara, Riz, Nuha, Asif, Davinder, Sunil, Rubic, N.A., N.A., N.A.
오버로드된 인덱서 (Overloaded Indexers)
인덱서는 오버로드 가능합니다. 여러 매개변수를 가질 수 있고, 각 매개변수는 다른 타입일 수 있습니다. 인덱스는 정수일 필요 없습니다. 문자열 등 다른 타입도 가능합니다.
문자열 인덱서 오버로드 예제
using System;
namespace IndexerApplication
{
class IndexedNames
{
private string[] nameList = new string[Size];
public static int Size = 10;
public IndexedNames()
{
for (int i = 0; i < Size; i++)
nameList[i] = "N.A.";
}
// 정수 인덱서
public string this[int index]
{
get
{
if (index >= 0 && index <= Size - 1)
return nameList[index];
return "";
}
set
{
if (index >= 0 && index <= Size - 1)
nameList[index] = value;
}
}
// 문자열 인덱서 (이름으로 인덱스 찾기)
public int this[string name]
{
get
{
for (int index = 0; index < Size; index++)
{
if (nameList[index] == name)
return index;
}
return -1; // 못 찾음
}
}
static void Main(string[] args)
{
IndexedNames names = new IndexedNames();
names[0] = "Zara";
names[1] = "Riz";
names[2] = "Nuha";
names[3] = "Asif";
names[4] = "Davinder";
names[5] = "Sunil";
names[6] = "Rubic";
// 정수 인덱서 사용
for (int i = 0; i < IndexedNames.Size; i++)
Console.WriteLine(names[i]);
// 문자열 인덱서 사용
Console.WriteLine(names["Nuha"]); // 2 출력
Console.ReadKey();
}
}
}
출력: Zara, Riz, Nuha, Asif, Davinder, Sunil, Rubic, N.A., N.A., N.A., 2
다차원 인덱서
여러 매개변수로 다차원 배열처럼 동작 가능.
public class Matrix
{
private double[,] data = new double[3, 3];
public double this[int row, int col]
{
get { return data[row, col]; }
set { data[row, col] = value; }
}
}
// 사용
Matrix m = new Matrix();
m[0, 1] = 3.14;
double val = m[0, 1];
인터페이스의 인덱서
public interface IStringContainer
{
string this[int index] { get; set; }
int Count { get; }
}
인덱서 vs 프로퍼티 비교
| 특성 | 프로퍼티 | 인덱서 |
| 이름 | 있음 (식별자) | 없음 (this 사용) |
| 매개변수 | 없음 | 최소 1개 (인덱스) |
| 접근 방식 | obj.Property | obj[index] |
| 용도 | 단일 값 캡슐화 | 컬렉션/시퀀스 요소 접근 |
| 오버로딩 | 이름으로 구분 | 매개변수 시그니처로 구분 |
| 정적 가능 | O | X (인스턴스 멤버만) |
모범 사례
- 컬렉션 의미론을 가진 클래스에만 인덱서 제공 (
List<T>, Dictionary<K,V> 등)
- 인덱스 범위 검사 필수 (예외 던지거나 기본값 반환)
get만 있는 읽기 전용 인덱서도 유용
- 복잡한 연산은 인덱서 대신 메서드 제공 (
FindByName() 등)
- 인덱서 타입은 의미에 맞게 (
int 인덱스, string 키 등)
핵심: 인덱서는 클래스를 배열처럼 직관적으로 사용할 수 있게 해주는 문법적 설탕입니다. 내부 저장소(배열, 리스트, 딕셔너리 등)를 숨기고 일관된 접근 인터페이스를 제공합니다.