인덱서 사용(C# 프로그래밍 가이드)

인덱서는 클라이언트 애플리케이션이 배열처럼 액세스할 수 있는 class, struct, interface를 만들 수 있게 해주는 편리한 구문입니다. 컴파일러는 Item 속성(또는 IndexerNameAttribute가 있는 경우 달리 명명된 속성) 및 적절한 접근자 메서드를 생성합니다. 인덱서는 내부 컬렉션 또는 배열을 캡슐화하는 데 주로 사용되는 형식에서 자주 구현됩니다. 예를 들어 24시간 동안 10회 기록된 화씨온도를 나타내는 TempRecord 클래스가 있다고 가정합니다. 이 클래스에는 온도 값을 저장할 float[] 형식의 temps 배열이 포함되어 있습니다. 이 클래스에서 인덱서를 구현하면 클라이언트가 TempRecord 인스턴스의 온도에 float temp = tempRecord.temps[4] 대신 float temp = tempRecord[4]로 액세스할 수 있습니다. 인덱서 표기법은 클라이언트 애플리케이션에 대한 구문을 간소화할 뿐 아니라 클래스와 해당 용도를 다른 개발자가 이해하기 쉽게 만듭니다.

클래스 또는 구조체에서 인덱서를 선언하려면 다음 예제에 표시된 대로 this 키워드를 사용합니다.

// Indexer declaration
public int this[int index]
{
    // get and set accessors
}

중요

인덱서를 선언하면 개체에 Item이라는 속성이 자동으로 생성됩니다. Item 속성은 멤버 액세스 식 인스턴스에서 직접 액세스할 수 없습니다. 또한 인덱서를 사용하여 Item 속성을 개체에 추가하는 경우 CS0102 컴파일러 오류가 표시됩니다. 이 오류를 방지하려면 아래에 설명된 대로 IndexerNameAttribute를 사용하여 인덱서 이름을 바꿉니다.

설명

인덱서의 형식과 해당 매개 변수의 형식은 최소한 인덱서 자체만큼 액세스 가능해야 합니다. 접근성 수준에 대한 자세한 내용은 액세스 한정자를 참조하세요.

인터페이스와 함께 인덱서를 사용하는 방법에 대한 자세한 내용은 인터페이스 인덱서를 참조하세요.

인덱서의 시그니처는 정식 매개 변수의 형식 및 개수로 구성됩니다. 정식 매개 변수의 이름이나 인덱서 형식은 포함되지 않습니다. 동일한 클래스에서 둘 이상의 인덱서를 선언하는 경우 다른 시그니처가 있어야 합니다.

인덱서는 변수로 분류되지 않습니다. 따라서 인덱서 값이 참조가 아니면 참조(ref 또는 out 매개 변수)로 인덱서 값을 전달할 수 없습니다(즉, 참조로 반환됨).

다른 언어에서 사용할 수 있는 이름을 인덱서에 제공하려면 다음 예제에 표시된 대로 System.Runtime.CompilerServices.IndexerNameAttribute를 사용합니다.

// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
    // get and set accessors
}

이 인덱서는 인덱서 이름 특성으로 재정의되므로 TheItem이라는 이름을 갖게 됩니다. 기본적으로 인덱서 이름은 Item입니다.

예제 1

다음 예제에서는 전용 배열 필드, temps, 인덱서를 선언하는 방법을 보여 줍니다. 인덱서를 사용하면 tempRecord[i] 인스턴스에 직접 액세스할 수 있습니다. 인덱서를 사용하지 않으려면 배열을 public 멤버로 선언하고 해당 멤버인 tempRecord.temps[i]에 직접 액세스합니다.

public class TempRecord
{
    // Array of temperature values
    float[] temps =
    [
        56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
        61.3F, 65.9F, 62.1F, 59.2F, 57.5F
    ];

    // To enable client code to validate input
    // when accessing your indexer.
    public int Length => temps.Length;
    
    // Indexer declaration.
    // If index is out of range, the temps array will throw the exception.
    public float this[int index]
    {
        get => temps[index];
        set => temps[index] = value;
    }
}

인덱서의 액세스가 Console.Write 문 등에서 평가될 때 get 접근자가 호출됩니다. 따라서 get 접근자가 없으면 컴파일 시간 오류가 발생합니다.

var tempRecord = new TempRecord();

// Use the indexer's set accessor
tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;

// Use the indexer's get accessor
for (int i = 0; i < 10; i++)
{
    Console.WriteLine($"Element #{i} = {tempRecord[i]}");
}

다른 값을 사용하여 인덱싱

C#은 인덱스 매개 변수 형식을 정수로 제한되지 않습니다. 예를 들어 인덱서와 함께 문자열을 사용하면 유용할 수 있습니다. 이러한 인덱서는 컬렉션에서 문자열을 검색하고 적절한 값을 반환하여 구현할 수 있습니다. 접근자를 오버로드할 수 있기 때문에 문자열 및 정수 버전을 함께 사용할 수 있습니다.

예제 2

다음 예제에서는 요일을 저장하는 클래스를 선언합니다. get 접근자는 문자열인 요일 이름을 사용하고 해당 정수를 반환합니다. 예를 들어 “일요일”이면 0을 반환하고, “월요일”이면 1을 반환합니다.

// Using a string as an indexer value
class DayCollection
{
    string[] days = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"];

    // Indexer with only a get accessor with the expression-bodied definition:
    public int this[string day] => FindDayIndex(day);

    private int FindDayIndex(string day)
    {
        for (int j = 0; j < days.Length; j++)
        {
            if (days[j] == day)
            {
                return j;
            }
        }

        throw new ArgumentOutOfRangeException(
            nameof(day),
            $"Day {day} is not supported.\nDay input must be in the form \"Sun\", \"Mon\", etc");
    }
}

예제 2 사용

var week = new DayCollection();
Console.WriteLine(week["Fri"]);

try
{
    Console.WriteLine(week["Made-up day"]);
}
catch (ArgumentOutOfRangeException e)
{
    Console.WriteLine($"Not supported input: {e.Message}");
}

예제 3

다음 예제에서는 System.DayOfWeek 열거형을 사용하여 요일을 저장하는 클래스를 선언합니다. get 접근자는 요일 값인 DayOfWeek를 사용하고 해당 정수를 반환합니다. 예를 들어 DayOfWeek.Sunday는 0을 반환하고 DayOfWeek.Monday는 1을 반환합니다.

using Day = System.DayOfWeek;

class DayOfWeekCollection
{
    Day[] days =
    [
        Day.Sunday, Day.Monday, Day.Tuesday, Day.Wednesday,
        Day.Thursday, Day.Friday, Day.Saturday
    ];

    // Indexer with only a get accessor with the expression-bodied definition:
    public int this[Day day] => FindDayIndex(day);

    private int FindDayIndex(Day day)
    {
        for (int j = 0; j < days.Length; j++)
        {
            if (days[j] == day)
            {
                return j;
            }
        }
        throw new ArgumentOutOfRangeException(
            nameof(day),
            $"Day {day} is not supported.\nDay input must be a defined System.DayOfWeek value.");
    }
}

예제 3 사용

var week = new DayOfWeekCollection();
Console.WriteLine(week[DayOfWeek.Friday]);

try
{
    Console.WriteLine(week[(DayOfWeek)43]);
}
catch (ArgumentOutOfRangeException e)
{
    Console.WriteLine($"Not supported input: {e.Message}");
}

강력한 프로그래밍

인덱서의 보안과 안정성을 향상할 수 있는 다음 두 가지 방법이 있습니다.

  • 클라이언트 코드에서 잘못된 인덱스 값을 전달할 가능성을 처리하는 일부 유형의 오류 처리 전략을 통합해야 합니다. 이 항목의 앞부분에 있는 첫 번째 예제에서 TempRecord 클래스는 클라이언트 코드에서 입력을 인덱서에 전달하기 전에 확인할 수 있게 해주는 Length 속성을 제공합니다. 인덱서 자체 안에 오류 처리 코드를 넣을 수도 있습니다. 사용자를 위해 인덱서 접근자 내에서 throw하는 모든 예외를 문서화해야 합니다.

  • getset 접근자의 접근성을 적절하게 제한적으로 설정합니다. 특히 set 접근자의 경우 이 작업이 중요합니다. 자세한 내용은 접근자 액세스 가능성 제한을 참조하세요.

참고 항목