C# indexers
last modified October 16, 2026
In this article we show how to define and use indexers in C#.
An indexer allows objects of a class or struct to be accessed with
the [] operator, in the same way as arrays. We use indexers every
day: list[0], dict["key"], or text[3] all
call indexers defined in the .NET types.
An indexer is declared like a property, but its name is the this
keyword followed by parameters in square brackets.
public T this[int index]
{
get { ... }
set { ... }
}
Unlike array indexes, indexer parameters can be of any type and an indexer can have multiple parameters. A type can have several indexers with different parameter lists.
C# simple indexer
In the first example, we create a collection class with an integer indexer.
var week = new Week();
Console.WriteLine(week[0]);
Console.WriteLine(week[6]);
week[6] = "Sun";
Console.WriteLine(week[6]);
try
{
Console.WriteLine(week[7]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine(e.Message);
}
class Week
{
private readonly string[] days =
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
public string this[int index]
{
get
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, days.Length);
return days[index];
}
set => days[index] = value;
}
}
The Week class wraps an array of day names.
public string this[int index]
The indexer takes an integer and returns a string.
ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, days.Length);
In the getter, we validate the index and throw an exception with a meaningful message.
set => days[index] = value;
In the setter, the assigned value is available through the implicit
value parameter.
$ dotnet run
Monday
Sunday
Sun
index ('7') must be less than '7'. (Parameter 'index')
Actual value was 7.
C# string indexer
The parameter of an indexer does not have to be an integer. In the next example, we access the values by name.
var settings = new Settings();
settings["theme"] = "dark";
settings["Language"] = "sk";
Console.WriteLine(settings["THEME"]);
Console.WriteLine(settings["language"]);
Console.WriteLine(settings["font"] ?? "not set");
class Settings
{
private readonly Dictionary<string, string> data =
new(StringComparer.OrdinalIgnoreCase);
public string? this[string key]
{
get => data.TryGetValue(key, out var value) ? value : null;
set
{
if (value is null)
{
data.Remove(key);
}
else
{
data[key] = value;
}
}
}
}
The Settings class stores values in a case-insensitive
dictionary. Unlike the dictionary indexer, our indexer returns
null for missing keys instead of throwing an exception, and
assigning null removes the key.
$ dotnet run dark sk not set
C# multi-parameter indexer
An indexer can take multiple parameters. This is natural for two-dimensional data such as matrices or game boards.
var m = new Matrix(3, 3);
for (int i = 0; i < 3; i++)
{
m[i, i] = 1;
}
m[0, 2] = 5;
Console.WriteLine(m);
class Matrix(int rows, int cols)
{
private readonly double[] data = new double[rows * cols];
public double this[int row, int col]
{
get => data[row * cols + col];
set => data[row * cols + col] = value;
}
public override string ToString()
{
var lines = new List<string>();
for (int r = 0; r < rows; r++)
{
var vals = Enumerable.Range(0, cols).Select(c => this[r, c]);
lines.Add(string.Join(" ", vals));
}
return string.Join(Environment.NewLine, lines);
}
}
The matrix stores its elements in a one-dimensional array; the indexer computes the position from the row and the column.
var vals = Enumerable.Range(0, cols).Select(c => this[r, c]);
Inside the class, the indexer is called with this[r, c].
$ dotnet run 1 0 5 0 1 0 0 0 1
C# overloaded and read-only indexers
A type can have several indexers with different parameter types. An indexer with only a getter is read-only; it can be written as an expression body.
var team = new Team(
[
new Player("Rooney", 10),
new Player("Giggs", 11),
new Player("Scholes", 18)
]);
Console.WriteLine(team[1]);
Console.WriteLine(team["Scholes"]);
Console.WriteLine(team["Beckham"]?.ToString() ?? "not found");
record Player(string Name, int Number);
class Team(Player[] players)
{
public Player this[int index] => players[index];
public Player? this[string name] =>
players.FirstOrDefault(p => p.Name == name);
}
The Team class can be indexed by a position or by a player
name.
$ dotnet run
Player { Name = Giggs, Number = 11 }
Player { Name = Scholes, Number = 18 }
not found
No player called Beckham is in the team; the indexer returns
null.
C# indices and ranges
If a type has an int indexer and a Count or
Length property, the compiler supports the hat operator
^ automatically. To support ranges, the type must also have a
Slice method.
var buf = new LogBook(["start", "load", "parse", "save", "exit"]);
Console.WriteLine(buf[0]);
Console.WriteLine(buf[^1]);
Console.WriteLine(buf[^2]);
var mid = buf[1..4];
Console.WriteLine(string.Join(", ", mid));
class LogBook(string[] entries)
{
public int Count => entries.Length;
public string this[int index] => entries[index];
public string[] Slice(int start, int length) =>
entries.AsSpan(start, length).ToArray();
}
We have not written any code for the ^ and ..
operators.
Console.WriteLine(buf[^1]);
The compiler translates buf[^1] to buf[buf.Count - 1].
var mid = buf[1..4];
The compiler translates the range to the Slice(1, 3) call.
$ dotnet run start exit save load, parse, save
C# indexers in interfaces
Interfaces can declare indexers. Implementing classes must provide them.
IGrid grid = new Board(3);
grid[1, 1] = 'X';
grid[0, 2] = 'O';
for (int r = 0; r < grid.Size; r++)
{
for (int c = 0; c < grid.Size; c++)
{
Console.Write(grid[r, c]);
}
Console.WriteLine();
}
interface IGrid
{
int Size { get; }
char this[int row, int col] { get; set; }
}
class Board : IGrid
{
private readonly char[,] cells;
public Board(int size)
{
cells = new char[size, size];
for (int r = 0; r < size; r++)
for (int c = 0; c < size; c++)
cells[r, c] = '.';
}
public int Size => cells.GetLength(0);
public char this[int row, int col]
{
get => cells[row, col];
set => cells[row, col] = value;
}
}
The IGrid interface declares a two-parameter indexer.
$ dotnet run ..O .X. ...
Source
Indexers - C# programming guide
In this article we have worked with indexers in C#.
Author
List all C# tutorials.