다음을 통해 공유


인덱서

클래스 또는 구조체의 인스턴스를 배열 또는 다른 컬렉션처럼 인덱싱할 수 있는 경우 인 덱서를 정의합니다. 인덱싱된 값은 형식 또는 인스턴스 멤버를 명시적으로 지정하지 않고 설정하거나 검색할 수 있습니다. 인덱서는 접근자가 매개 변수를 사용한다는 점을 제외하고 속성 과 유사합니다.

다음 예제에서는 값을 할당하고 검색하는 메서드와 get 접근자를 사용하여 제네릭 클래스 set 를 정의합니다.

namespace Indexers;

public class SampleCollection<T>
{
   // Declare an array to store the data elements.
   private T[] arr = new T[100];

   // Define the indexer to allow client code to use [] notation.
   public T this[int i]
   {
      get => arr[i];
      set => arr[i] = value;
   }
}

앞의 예제에서는 읽기/쓰기 인덱서를 보여줍니다. 여기에는 get 접근자와 set 접근자가 모두 포함됩니다. 다음 예제와 같이 읽기 전용 인덱서를 식 본문 멤버로 정의할 수 있습니다.

namespace Indexers;

public class ReadOnlySampleCollection<T>(params IEnumerable<T> items)
{
   // Declare an array to store the data elements.
   private T[] arr = [.. items];

   public T this[int i] => arr[i];

}

get 키워드가 사용되지 않습니다; =>가 식 본문을 도입합니다.

인덱서는 하나 이상의 인수를 사용하여 참조되는 속성인 인덱싱된 속성을 사용하도록 설정합니다. 이러한 인수는 일부 값 컬렉션에 인덱스 제공

  • 인덱서를 사용하면 배열과 유사한 개체를 인덱싱할 수 있습니다.
  • get 접근자가 값을 반환합니다. set 접근자가 값을 할당합니다.
  • 키워드는 this 인덱서 정의합니다.
  • value 키워드는 set 접근자의 인수입니다.
  • 인덱서에는 정수 인덱스 값이 필요하지 않습니다. 특정 조회 메커니즘을 정의하는 방법은 사용자에게 달려 있습니다.
  • 인덱서를 오버로드할 수 있습니다.
  • 예를 들어 2차원 배열에 액세스할 때 인덱서에는 하나 이상의 공식 매개 변수가 있을 수 있습니다.
  • 형식에서 인덱서를 선언partialpartial 수 있습니다.

속성 작업에서 배운 거의 모든 것을 인덱서에 적용할 수 있습니다. 해당 규칙의 유일한 예외는 자동으로 구현되는 속성입니다. 컴파일러가 인덱서에 대한 올바른 스토리지를 항상 생성할 수 있는 것은 아닙니다. 각 인덱서의 인수 목록이 고유하기만 하면 형식에 여러 인덱서를 정의할 수 있습니다.

인덱서 사용

API가 일부 컬렉션을 모델화할 때 형식에서 인덱서를 정의합니다. 인덱서는 .NET Core 프레임워크의 일부인 컬렉션 형식에 직접 매핑할 필요가 없습니다. 인덱서를 사용하면 해당 추상화에 대한 값을 저장하거나 계산하는 방법에 대한 내부 세부 정보를 노출하지 않고도 형식의 추상화와 일치하는 API를 제공할 수 있습니다.

배열 및 벡터

형식이 배열 또는 벡터를 모델링할 수 있습니다. 사용자 고유의 인덱서 만들기의 장점은 필요에 맞게 해당 컬렉션에 대한 스토리지를 정의할 수 있다는 것입니다. 유형이 너무 커서 한 번에 메모리에 로드할 수 없는 역사적 데이터를 모델링하는 상황을 상상해 보십시오. 사용량에 따라 컬렉션의 섹션을 로드하고 언로드해야 합니다. 다음 예제에서는 이 동작을 모델화합니다. 데이터 포인트의 수를 보고합니다. 요청 시 데이터의 섹션을 보관하는 페이지를 만듭니다. 메모리에서 페이지를 제거하여 최근 요청에 필요한 페이지의 공간을 확보합니다.

namespace Indexers;

public record Measurements(double HiTemp, double LoTemp, double AirPressure);

public class DataSamples
{
    private class Page
    {
        private readonly List<Measurements> pageData = new ();
        private readonly int _startingIndex;
        private readonly int _length;

        public Page(int startingIndex, int length)
        {
            _startingIndex = startingIndex;
            _length = length;

            // This stays as random stuff:
            var generator = new Random();
            for (int i = 0; i < length; i++)
            {
                var m = new Measurements(HiTemp: generator.Next(50, 95),
                    LoTemp: generator.Next(12, 49),
                    AirPressure: 28.0 + generator.NextDouble() * 4
                );
                pageData.Add(m);
            }
        }
        public bool HasItem(int index) =>
            ((index >= _startingIndex) &&
            (index < _startingIndex + _length));

        public Measurements this[int index]
        {
            get
            {
                LastAccess = DateTime.Now;
                return pageData[index - _startingIndex];
            }
            set
            {
                pageData[index - _startingIndex] = value;
                Dirty = true;
                LastAccess = DateTime.Now;
            }
        }

        public bool Dirty { get; private set; } = false;
        public DateTime LastAccess { get; set; } = DateTime.Now;
    }

    private readonly int _totalSize;
    private readonly List<Page> pagesInMemory = new ();

    public DataSamples(int totalSize)
    {
        this._totalSize = totalSize;
    }

    public Measurements this[int index]
    {
        get
        {
            if (index < 0) throw new IndexOutOfRangeException("Cannot index less than 0");
            if (index >= _totalSize) throw new IndexOutOfRangeException("Cannot index past the end of storage");

            var page = updateCachedPagesForAccess(index);
            return page[index];
        }
        set
        {
            if (index < 0) throw new IndexOutOfRangeException("Cannot index less than 0");
            if (index >= _totalSize) throw new IndexOutOfRangeException("Cannot index past the end of storage");
            var page = updateCachedPagesForAccess(index);

            page[index] = value;
        }
    }

    private Page updateCachedPagesForAccess(int index)
    {
        foreach (var p in pagesInMemory)
        {
            if (p.HasItem(index))
            {
                return p;
            }
        }
        var startingIndex = (index / 1000) * 1000;
        var newPage = new Page(startingIndex, 1000);
        addPageToCache(newPage);
        return newPage;
    }

    private void addPageToCache(Page p)
    {
        if (pagesInMemory.Count > 4)
        {
            // remove oldest non-dirty page:
            var oldest = pagesInMemory
                .Where(page => !page.Dirty)
                .OrderBy(page => page.LastAccess)
                .FirstOrDefault();
            // Note that this may keep more than 5 pages in memory
            // if too much is dirty
            if (oldest != null)
                pagesInMemory.Remove(oldest);
        }
        pagesInMemory.Add(p);
    }
}

이 디자인 관용구에 따라 전체 데이터 집합을 메모리 내 컬렉션에 로드하지 않는 좋은 이유가 있는 모든 종류의 컬렉션을 모델링할 수 있습니다. 클래스는 Page 공용 인터페이스의 일부가 아닌 프라이빗 중첩 클래스입니다. 이러한 세부 정보는 이 클래스의 사용자로부터 숨겨집니다.

사전

또 다른 일반적인 시나리오는 사전 또는 맵을 모델링해야 하는 경우입니다. 이 시나리오는 형식이 키를 기반으로 값을 저장하는 경우로, 키는 아마도 텍스트 키일 것입니다. 이 예제에서는 명령줄 인수를 해당 옵션을 관리하는 람다 식에 매핑하는 사전을 만듭니다. 다음 예제에서는 명령줄 옵션을 ArgsActions 대리자와 매핑하는 System.Action 클래스와 해당 옵션을 발견할 때 ArgsProcessor를 사용하여 각 ArgsActions을 실행하는 Action 클래스를 보여 줍니다.

namespace Indexers;
public class ArgsProcessor
{
    private readonly ArgsActions _actions;

    public ArgsProcessor(ArgsActions actions)
    {
        _actions = actions;
    }

    public void Process(string[] args)
    {
        foreach (var arg in args)
        {
            _actions[arg]?.Invoke();
        }
    }

}
public class ArgsActions
{
    readonly private Dictionary<string, Action> _argsActions = new();

    public Action this[string s]
    {
        get
        {
            Action? action;
            Action defaultAction = () => { };
            return _argsActions.TryGetValue(s, out action) ? action : defaultAction;
        }
    }

    public void SetOption(string s, Action a)
    {
        _argsActions[s] = a;
    }
}

이 예제에서 컬렉션은 ArgsAction 기본 컬렉션에 밀접하게 매핑됩니다. 지정된 get 옵션이 구성되어 있는지 여부를 결정합니다. 이 경우 해당 옵션과 연결된 값을 반환 Action 합니다. 그렇지 않은 경우 아무 것도 수행하지 않는 값을 Action 반환합니다. 공용 접근자에는 set 접근자가 포함되어 있지 않습니다. 대신, 디자인은 옵션을 설정하기 위해 공용 메서드를 사용합니다.

다차원 맵

여러 인수를 사용하는 인덱서를 만들 수 있습니다. 또한 이러한 인수는 동일한 형식으로 제한되지 않습니다.

다음 예제에서는 Mandelbrot 집합에 대한 값을 생성하는 클래스를 보여 줍니다. 집합 뒤에 있는 수학에 대한 자세한 내용은 이 문서를 참조하세요. 인덱서는 두 개의 double을 사용하여 X, Y 평면의 점을 정의합니다. 접근자는 get 점이 집합에 없는 것으로 확인될 때까지 반복 횟수를 계산합니다. 최대 반복 횟수에 도달하면 점이 집합에 있으며 클래스의 maxIterations 값이 반환됩니다. (만델브로트 집합에 대중화된 컴퓨터에서 생성된 이미지는 점이 집합 외부에 있는지 확인하는 데 필요한 반복 횟수에 대한 색을 정의합니다.)

namespace Indexers;
public class Mandelbrot(int maxIterations)
{

    public int this[double x, double y]
    {
        get
        {
            var iterations = 0;
            var x0 = x;
            var y0 = y;

            while ((x * x + y * y < 4) &&
                (iterations < maxIterations))
            { 
                (x, y) = (x * x - y * y + x0, 2 * x * y + y0);
                iterations++;
            }
            return iterations;
        }
    }
}

만델브로트 집합은 실수 값에 대해 각 (x,y) 좌표에서 값을 정의합니다. 무한 수의 값을 포함할 수 있는 사전을 정의합니다. 따라서 세트 뒤에 저장 공간이 없습니다. 대신, 이 클래스는 코드가 접근자를 호출할 때 각 지점에 대한 값을 계산합니다 get . 기본 스토리지는 사용되지 않습니다.

요약

클래스에 속성과 유사한 요소가 있을 때마다 인덱서를 만듭니다. 여기서 해당 속성은 단일 값이 아니라 값 집합을 나타냅니다. 하나 이상의 인수는 각 개별 항목을 식별합니다. 이러한 인수는 집합에서 참조해야 하는 항목을 고유하게 식별할 수 있습니다. 인덱서는 속성의 개념을 확장합니다. 여기서 멤버는 클래스 외부에서 데이터 항목처럼 처리되지만 내부 메서드처럼 처리됩니다. 인덱서를 사용하면 인수가 항목 집합을 나타내는 속성에서 단일 항목을 찾을 수 있습니다.

인덱서의 샘플 폴더에 액세스할 수 있습니다. 다운로드 지침은 샘플 및 자습서참조하세요.

C# 언어 사양

자세한 내용은 C# 언어 사양인덱서를 참조하세요. 언어 사양은 C# 구문 및 사용의 최종 소스입니다.