CodeGym /행동 /C# SELF /Extension Members: 인덱서

Extension Members: 인덱서

C# SELF
레벨 18 , 레슨 4
사용 가능

1. 소개

상상해봐, 우리가 좋아하는 DogShelter 클래스가 있고, 거기엔 강아지 컬렉션이 저장돼 있어. 지난 강의에서 이미 인덱서를 추가해서 강아지 번호로 가져올 수 있었지: Dog firstDog = myShelter[0];. 완전 멋지지!

근데 만약 사용자가 번호가 아니라, 예를 들어 이름으로 강아지를 찾고 싶으면 어떡하지? 아니면 품종으로? 아니면 여러 조건 조합으로? 물론 GetDogByName("Buddy")GetDogByBreedAndAge("Labrador", 5) 같은 메서드를 추가할 수도 있어. 이건 완전 정상적인 방법이야.

하지만 가끔은 더 "배열처럼" 직관적으로 접근하고 싶을 때가 있잖아. 예를 들어 이렇게 쓰고 싶은 거지: Dog buddy = myShelter["Buddy"]; 또는 Dog oldLab = myShelter["Labrador", 8];.

DogShelter가 우리 코드라면 그냥 인덱서를 추가하면 돼. 근데 만약 DogShelter가 외부 라이브러리 클래스라서 수정 못 한다면? 아니면 아주 특이한 접근법을 추가하고 싶은데, 원본 클래스를 "더럽히고" 싶지 않다면?

바로 여기서 확장 인덱서(Extension Indexers)가 등장하는 거야!

2. "대괄호"를 밖에서 붙이기

지난 강의에서 DisplayName 확장 프로퍼티를 Dog에 추가했던 거 기억나? 인덱서도 거의 똑같이 동작해!

확장 인덱서는 static 클래스에 정의된 static 인덱서로, 이미 존재하는 타입의 객체에 obj[인덱스] 문법을 쓸 수 있게 해줘. 원래 그 타입에 인덱서가 없었거나, 다른 타입의 인덱서를 추가하고 싶을 때도 쓸 수 있어.

이건 마치 냉장고를 샀는데, 특정 부분을 두드리면 콜라가 나오는 기능을 나중에 추가한 것과 비슷해. 냉장고는 그대로인데, 기능이 "밖에서" 추가된 거지!

확장 인덱서 문법


public static class MyExtensionClass
{
    extension(ObjectType 인스턴스)
    {    
        public static ReturnType this[인덱스타입 index ]
        {
            get
            {
                // 읽기 로직, 인스턴스index 사용
                return ...;
            }
            set
            {
                // 인스턴스, index 그리고 'value' 키워드 사용
                // 'value'는 새 값이야
            }
        }
    }        
}
확장 인덱서(Extension Indexer) 문법

this 확장대상타입 인스턴스에 주목! 이 문법은 우리가 확장 메서드나 프로퍼티에서 봤던 거랑 똑같아. 인스턴스는 우리가 확장하는 객체를 getset 액세서 안에서 부를 이름이야.

3. Extension Indexer 선언하기 (머리 안 터지게!)

문법은 지난 강의에서 다뤘던 Extension Properties랑 비슷한데, 인덱스 파라미터가 추가된 거야. 최소 예제는 이래:


public static class DogShelterExtensions
{
    extension(DogShelter shelter)
    {      
        public static Dog this[string name]
        {
            get
            {
                foreach (var dog in shelter)
                {
                    if (dog.Name == name)
                        return dog;
                }
                return null;
            }
        }
    }        
}
DogShelter에 이름으로 찾는 Extension Indexer

익숙한 요소들:

  • this가 첫 파라미터 앞에 붙는 건 Extension Members(확장 대상 객체) 규칙이야.
  • 클래스 이름 뒤에 대괄호 안에서 쓸 파라미터들이 나와.
거의 일반 인덱서랑 똑같이 동작하지만, 원본 클래스를 안 건드려도 돼!

실습: DogShelter에 이름 인덱서 확장하기

우리 예제 프로젝트를 살짝 바꿔보자. 강아지 보호소가 있고, 각 강아지는 이름이 유니크하다고 해보자:

DogShelter 클래스 (라이브러리/외부 코드)

public class Dog
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class DogShelter : IEnumerable<Dog>
{
    private List<Dog> dogs = new List<Dog>();

    public void AddDog(Dog dog) => dogs.Add(dog);

    // 기존 번호 인덱서
    public Dog this[int index]
    {
        get => dogs[index];
        set => dogs[index] = value;
    }

    public IEnumerator<Dog> GetEnumerator() => dogs.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

원하는 것: shelter["부샤"]

예전엔 — 메서드로만 가능:

// C# 14 이전:
public static Dog? FindByName(this DogShelter shelter, string name) { ... }

이제는 — Extension Indexer로!


public static class DogShelterExtensions
{
    extension(DogShelter shelter)
    {    
        public static Dog? this[string name]
        {
            get
            {
                foreach (var dog in shelter)
                    if (dog.Name == name)
                        return dog;
                return null; 
            }
            set
            {
                for (int i = 0; i < shelter.Count; i++)
                {
                    if (shelter[i].Name == name)
                    {
                        shelter[i] = value!;
                        return;
                    }
                }
                throw new ArgumentException("강아지를 찾을 수 없음");
            }
        }
    }        
}

이제 메인 코드가 훨씬 깔끔해졌지:

var shelter = new DogShelter();
shelter.AddDog(new Dog { Name = "부샤", Age = 3 });
shelter.AddDog(new Dog { Name = "투직", Age = 5 });

// extension-인덱서 사용!
Dog busya = shelter["부샤"]!;
Console.WriteLine(busya.Age);

shelter["부샤"] = new Dog { Name = "부샤", Age = 4 };

시각화: 실제로 어떻게 동작할까?

동작 예전 방식 Extension Indexer
이름으로 찾기 shelter.FindByName("X") shelter["X"]
이름으로 강아지 수정 shelter.UpdateName("X", ..) shelter["X"] = ...

4. Extension Indexers의 디테일과 특징

컴파일러와 범위

  • Extension Indexer는 public static 클래스에 선언해야 해 (extension methods랑 똑같이).
  • 필요한 using을 꼭 추가해야 해. 까먹으면 컴파일러는 조용한데, 코드는 안 돌아감.
  • 기본 클래스에 이미 같은 인덱서가 있으면 확장 불가(시그니처가 달라야 함).

set 액세서 구현

get만 선언하면 읽기 전용 인덱서가 돼. set도 추가하면(위 예제처럼) 읽고 쓸 수 있어.

값/참조 전달

Extension Indexer는 확장하는 객체 인스턴스(this가 첫 파라미터)에 동작해. 객체가 참조 타입이면 상태를 바꿀 수 있어.

한 클래스에 여러 인덱서

문제 없어 — 파라미터 조합만 다르면 여러 extension-인덱서 선언 가능! 예를 들어 나이로 찾기: shelter[5](기존), shelter["부샤"](새로운), shelter[age: 3](또 다른, 원하면).

예시: DogShelter에 인덱서 두 개 추가


public static class DogShelterExtensions
{
    extension(DogShelter shelter)
    {       
        // 이름으로
        public static Dog? this[string name]
        {
            get => shelter.FirstOrDefault(d => d.Name == name);
            set
            {
                for (int i = 0; i < shelter.Count; i++)
                    if (shelter[i].Name == name)
                        shelter[i] = value!;
            }
        }

        // 나이로 — 해당 나이의 첫 강아지 반환
        public static Dog? this[int age]
        {
            get => shelter.FirstOrDefault(d => d.Age == age);
        }
    }        
}

이제 이렇게 쓸 수 있어:

var youngDog = shelter[1];           // 나이로
var tony = shelter["토니"];          // 이름으로
shelter["투직"] = new Dog { Name = "투직", Age = 9 };

실제 사용 예시

  • 외부 라이브러리: 외부 클래스에 인덱싱 방법을 추가하고 싶을 때, 원본 소스 안 건드리고. 예를 들어 주문 컬렉션에서 번호, 날짜, 상태 등으로 찾고 싶을 때, 래퍼 메서드 중복 없이 가능.
  • "어댑터 패턴": 구식 컬렉션 API를 더 현대적이고 C#스러운 스타일로 바꿔주면서, 기존 코드 호환성도 유지.
  • 레거시 코드 마이그레이션: 이미 작성된 타입에 새 기능을 추가하면서 기존 코드와 테스트는 그대로 둘 수 있음.
  • 테스트 편의성: 임시로 인덱서를 붙여서(예: 테스트에만 필요한 특이한 검색), 메인 클래스를 더럽히지 않고 쓸 수 있음.

5. Extension Indexers 쓸 때 흔한 실수와 함정

만약 기본 클래스에 똑같은 시그니처의 인덱서가 이미 있으면, extension-인덱서는 호출되지 않아 — 기본 인덱서가 우선이야.

Extension-인덱서도 extension member라서, using(네임스페이스 import) 없으면 확장이 안 보여.

또 자주 하는 실수 — null을 반환하면서 사용자에게 알리지 않는 것. 누가 실수로 없는 요소에 접근했는데 extension-인덱서가 null을 주면, 다른 코드에서 NullReferenceException이 날 수 있어. 좋은 습관은, 예외를 던질지, 특별한 더미 객체를 줄지, 그냥 null을 줄지 미리 생각해두는 거야.

여러 extension-인덱서를 만들 땐, 파라미터 타입과 개수로 시그니처가 유니크한지 꼭 확인해. 똑같은 시그니처 두 개 만들면 컴파일 에러야.

Extension-인덱서는 객체 인스턴스에만 동작하고, static 타입에는 안 돼.

1
설문조사/퀴즈
인덱서와의 첫 만남, 레벨 18, 레슨 4
사용 불가능
인덱서와의 첫 만남
인덱서랑 Extension Members
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION