Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Индексаторы определяются, когда экземпляры класса или структуры можно индексировать как массив или другую коллекцию. Индексированное значение можно задать или извлечь без явного указания типа или элемента экземпляра. Индексаторы похожи на свойства , за исключением того, что их методы доступа принимают параметры.
В следующем примере определяется универсальный класс с методами доступа 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
для аксессора. - Индексаторы не требуют целочисленного значения индекса; Это зависит от того, как определить конкретный механизм поиска.
- Индексаторы могут быть перегружены.
- Индексаторы могут иметь один или несколько формальных параметров, например при доступе к двухмерным массивам.
- Индексаторы можно объявить
partial
вpartial
типах.
Вы можете применить практически все, что вы узнали о работе со свойствами к индексаторам. Единственное исключение из этого правила — это автоматически реализуемые свойства. Компилятор не всегда может создать правильное хранилище для индексатора. Можно определить несколько индексаторов в типе, если списки аргументов для каждого индексатора уникальны.
Использование индексаторов
Вы определяете индексаторы в вашем типе, когда 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. Дополнительные сведения о математике, стояшей за набором, см. в этой статье. Индексатор использует два двойника для определения точки в плоскости X, Y. Аксессор get
вычисляет количество итераций, пока точка не будет определена как не находящаяся в наборе. Когда достигается максимальное число итераций, точка находится в наборе, а значение maxIterations класса возвращается. (Созданные компьютером изображения, популярные для набора Mandelbrot, определяют цвета для количества итераций, необходимых для определения того, что точка находится за пределами набора.)
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;
}
}
}
Набор Mandelbrot определяет значения в каждой координате (x,y) для реальных значений чисел. Это определяет словарь, который может содержать бесконечное число значений. Поэтому за установкой нет места для хранения. Вместо этого этот класс вычисляет значение для каждой точки при вызове метода доступа get
. Базовое хранилище не используется.
Суммирование
Вы создаете индексаторы в любой момент, когда в классе есть элемент, похожий на свойства, где это свойство представляет не одно значение, а скорее набор значений. Один или несколько аргументов определяют каждый отдельный элемент. Эти аргументы могут однозначно определить, на какой элемент в наборе следует ссылаться. Индексаторы расширяют концепцию свойств, где член обрабатывается как элемент данных извне класса, но как метод внутри. Индексаторы позволяют аргументам находить один элемент в свойстве, представляющего набор элементов.
Вы можете получить доступ к образцу папки для индексаторов. Инструкции по скачиванию смотрите в разделах Образцы и руководства.
Спецификация языка C#
Дополнительные сведения см. в разделе "Индексаторы " в спецификации языка C#. Спецификация языка является авторитетным источником синтаксиса и использования языка C#.