Indexers (C# Programming Guide)
Indexers - это механизм в C#, позволяющий работать с объектом класса или структурой как с массивом, обращаясь к его элементам по индексам. Например так: plantPoolInstance[elementIndex]
.
Для того чтобы использовать indexer нужно определить один метод как показано ниже
...
// определение indexer-a
public Plant this[int i]
{
// getter, обрабатывающий запись в массив
// plantPool - переменная в которой хранятся данные. Например массив растений.
// эта переменная должны быть объявлена и инициализирована в классе или доступна из него.
get { return plantPool[i]; }
// setter, обрабатываюзий чтение из массива
set { plantPool[i] = value; }
}
...
this
указывает на то, что это indexer.
[int i]
- это параметры индексатора.
Далее, чуть более подробный пример:
internal class Program
{
class Plant
{
public string Name { get; init; }
}
class PlantPool
{
private Plant[] plantPool { get; init; }
public PlantPool(int Capacity) { this.plantPool = new Plant[Capacity]; }
// тут определяем indexer с одним параметрам типа int
public Plant this[int i]
{
get { return plantPool[i]; }
set { plantPool[i] = value; }
}
}
static void Main(string[] args)
{
int Capacity = 2;
var plantPool = new PlantPool(Capacity);
plantPool[0] = new Plant() { Name = "Bétula péndula" };
plantPool[1] = new Plant() { Name = "Betula papyrifera" };
for (int i=0; i < Capacity; i++)
{
Console.WriteLine(plantPool[i].Name);
}
}
}
Идея довольно проста, мы определяем две функции для организации работы в решими массива и добавляем немного магии this[int i]
чтобы компилятор понял что мы хотим.
Индексатор не может быть статических членом класса.
Мы не ограничены целими числами в параметрах индекса, как в примере выше, также нет ограничений на количеством агрументов.
Немного старнный пример с вариантоми использоватья аругметов
...
class PlantPool
{
private Plant[,] plantPool { get; init; }
public PlantPool(int Capacity) { this.plantPool = new Plant[Capacity,Capacity]; }
// - один или более пареметров
// - разные типы параметров
public Plant this[int i, string j]
{
get { return plantPool[i, int.Parse(j)]; }
set { plantPool[i, int.Parse(j)] = value; }
}
}
static void Main(string[] args)
{
int Capacity = 2;
var plantPool = new PlantPool(Capacity);
plantPool[0, "0"] = new Plant() { Name = "Bétula péndula" };
plantPool[1, "1"] = new Plant() { Name = "Betula papyrifera" };
for (int i=0; i < Capacity; i++)
{
Console.WriteLine(plantPool[i, i.ToString()].Name);
}
}
...
В целом, возможность использовать разные парметры предоставляет довольно большую гибкость при написании кода.
Допускается перегрузка indexer-a, что дает возможность писать такой код:
...
// indexer for int, string
public Plant this[int i, string j]
{
get { return plantPool[i, int.Parse(j)]; }
set { plantPool[i, int.Parse(j)] = value; }
}
// indexer for int, int
public Plant this[int i, int j]
{
get { return plantPool[i, j]; }
set { plantPool[i, j] = value; }
}
...
// and then
plantPool[0, 0] = new Plant() { Name = "Bétula péndula" };
plantPool[1, 1] = new Plant() { Name = "Betula papyrifera" };
for (int i=0; i < Capacity; i++)
{
Console.WriteLine(plantPool[i, i]);
}
Indexer может быть объявлен в интерфейсе. Методы доступа остаются без реализации.
interface IPlantPool
{
Plant this[int i, int j]
{
get;
set;
}
}
Реализация такого интерфейса:
class PlantPool : IPlantPool
{
private Plant[,] plantPool { get; init; }
public PlantPool(int Capacity) { this.plantPool = new Plant[Capacity,Capacity]; }
public Plant this[int i, int j]
{
get { return plantPool[i, j]; }
set { plantPool[i, j] = value; }
}
}
Никто нам не мешает потребовать реализации только одного метода доступа. Например
interface IPlantPool
{
Plant this[int i, int j]
{
get;
}
}
а реализовать еще и set
или ограничиться только тем что требует интерфейс.
Больше фич, поддерживаемых интерфейсами можно найти в C# vs Java. Интерфейсы в C#.
Это возможно реализовать индексатор целиком в интерфейсе.
Приступая к следующеми примеру - стоит понимать что такое Интерфейсы в C#.
interface IPlantPool
{
static int Capacity = 2;
// мы не можем инициализировать non-static поля прям в интерфейсе
// но со static полем все получилось
static Plant[,] plantPool { get; set; } = new Plant[Capacity, Capacity];
// нельзя использовать init, но можно set
// методы по умолчанию могут реализовать indexer
Plant this[int i, int j]
{
get { return plantPool[i, j]; }
set { plantPool[i, j] = value; }
}
}
// Реализуем интерфейс
class PlantPool : IPlantPool
{
}
static void Main(string[] args)
{
// так как default методы будут доступны для интерфейса IPlantPool,
// сразу приведем инстанс к типу интерфейса
IPlantPool plantPool = new PlantPool();
plantPool[0, 0] = new Plant() { Name = "Bétula péndula" };
plantPool[1, 1] = new Plant() { Name = "Betula papyrifera" };
// используем Capacity из интерфейса, чтобы узнать границы индекса
for (int i=0; i < IPlantPool.Capacity; i++)
{
Console.WriteLine(plantPool[i, i]);
}
}
В Java нет столько сахара 😉 возможности переопределять операторы, включая обращение по индексу.