Skip to main content

C# vs Java. Indexers

· 5 min read
Andrey Ganyushkin

Indexers (C# Programming Guide)

C#

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] чтобы компилятор понял что мы хотим.

Индексатор не может быть статических членом класса.

Аргументы indexer-a

Мы не ограничены целими числами в параметрах индекса, как в примере выше, также нет ограничений на количеством агрументов.

Немного старнный пример с вариантоми использоватья аругметов

...
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-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 в интерфейсах

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

В Java нет столько сахара 😉 возможности переопределять операторы, включая обращение по индексу.