Understanding Indexers in C#
When learning C#, you often use properties to expose fields of a class. However, when a class contains multiple values that can be accessed using an index, C# provides a special feature called an Indexer.
An indexer allows an object to be used in the same way as an array.
What Is an Indexer?
An Indexer is a parameterized property. It allows objects of a class to be accessed using square brackets ([]) just like arrays.
For example:
- The object of Customer class with index 0 will be customer[0] will refer to a field of Customer class.
- The object of Employee class with index 2 will be employee[2] will refer to a field of Employee class.
Instead of calling a method such as: customer.GetValue(0); an indexer provides a more natural syntax.
Characteristics of an Indexer
- An indexer is a parameterized property.
- Unlike ordinary properties, an indexer has no name. It is represented by the keyword this followed by square brackets containing one or more parameters.
- A property is generally used to expose a single field, whereas an indexer is commonly used to expose a collection of values or multiple related values.
- An indexer contains get and set accessors just like a property.
- The get accessor returns a value based on the supplied index.
- The set accessor stores a value at the supplied index.
- You can use local variables, if statements, switch statements, loops, and other logic inside an indexer.
- Indexer parameter i.e. index need not necessarily be an integer; it can be integer, boolean or string type etc.
- Indexer return type must match the return type of the fields which it can deal with for get and set operations.
Example 1: Using an Indexer to Access Multiple Fields
Suppose a Customer class contains three fields: Id, Age, and Salary. An indexer can be used to access them through index positions.
public class Customer
{
private int _id;
private int _age;
private int _salary;
public int this[int index]
{
get
{
switch (index)
{
case 0: return _id;
case 1: return _age;
case 2: return _salary;
default:
throw new Exception("The index can be 0, 1 or 2");
}
}
set
{
switch (index)
{
case 0: _id = value; break;
case 1: _age = value; break;
case 2: _salary = value; break;
}
}
}
}
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
customer[0] = 100;
customer[1] = 32;
customer[2] = 50000;
Console.WriteLine(
$"The id, age and salary are " +
$"{customer[0]}, {customer[1]} and {customer[2]} respectively.");
}
}
Output
The id, age and salary are 100, 32 and 50000 respectively.
How It Works
Here the index positions represent different fields:
| Index | Field |
| 0 | Id |
| 1 | Age |
| 2 | Salary |
- When customer[1] is executed, the get accessor runs and returns _age.
- When customer[1] = 32 is executed, the set accessor runs and stores the value in _age.
Another example to access different fields of a class using indexer is as follows. In this example, indexer returns string type data for an index.
Example 2: Using an Indexer to Access Multiple Fields
public class Employee
{
private string _name;
private string _city;
private string _designation;
public string this[int index]
{
get
{
switch (index)
{
case 0: return "The name of Employee is " + _name;
case 1: return "The city of Employee is " + _city;
case 2: return "The designation of Employee is " + _designation;
default: throw new Exception("The index can be 0, 1 or 2"); //return -1;
}
}
set
{
switch (index)
{
case 0: _name = value; break;
case 1: _city = value; break;
case 2: _designation = value; break;
}
}
}
}
class Program
{
static void Main(string[] args)
{
Employee employee = new Employee();
employee[0] = "Ajeet";
employee[1] = "Delhi";
employee[2] = "Software Engineer";
Console.WriteLine($"{employee[0]}\n{employee[1]}\n{employee[2]}");
}
}
Example 3: Using an Indexer with an Array
A more common use of indexers is to expose a collection such as an array.
public class Customer
{
public string[]? Names = null;
public string this[int index]
{
get
{
if (Names != null &&
index >= 0 &&
index < Names.Length)
{
return Names[index];
}
return "Unknown";
}
set
{
if (Names != null &&
index >= 0 &&
index < Names.Length)
{
Names[index] = value;
}
}
}
}
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
customer.Names = new string[]
{
"John",
"Jane",
"Doe"
};
Console.WriteLine(customer[0]);
Console.WriteLine(customer[1]);
Console.WriteLine(customer[2]);
}
}
Output
John
Jane
Doe
How It Works
The class contains an array called Names. The indexer uses the supplied index to access an element of the array: customer[0] is internally handled by: Names[0] Similarly: customer[1] = "Mary"; updates the second element of the array.
The indexer also performs validation to ensure that:
- The array is not null.
- The index is within the valid range.
If an invalid index is supplied, the get accessor returns "Unknown".
Indexers vs Properties
|
Property
|
Indexer
|
|
Has a name
|
Has no name
|
|
Accessed using dot operator
|
Accessed using square brackets
|
|
Usually exposes a single value
|
Usually exposes multiple values
|
|
Example: customer.Name
|
Example: customer[0]
|
Example property:
- public string Name { get; set; }
Example indexer:- public string this[int index] { get; set; }
Conclusion
An indexer allows an object to be treated like an array. It is essentially a parameterized property that uses the this keyword and one or more parameters to provide indexed access to data.
Indexers are especially useful when:
- A class contains a collection such as an array or list.
- You want users of the class to access data using array-like syntax.
- You want to hide the internal storage details while providing a simple interface.
By using indexers, objects become easier and more intuitive to work with, especially when they manage multiple related values.
Example 4: Using an Indexer to Access Rectangular Array
Suppose a Rectange class contains a Rectangular Array. An indexer can be used to access elemenets of this Rectangular Array.
public class Rectangle
{
private int[,] table = new int[3, 4];
public int this[int row, int col]
{
get
{
if (row >= 0 && col >= 0)
{
return table[row, col];
}
else
{
return -1;
}
}
set
{
if (row >= 0 && col >= 0)
{
table[row, col] = value;
}
else
{
throw new IndexOutOfRangeException("Invalid index");
}
}
}
}
class Program
{
static void Main(string[] args)
{
Rectangle rectangle = new Rectangle();
// initialize table using indexer of Rectangle
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
rectangle[i, j] = i + j + 100;
}
}
// print table using indexer
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
Console.Write(rectangle[i, j] + " ");
}
Console.WriteLine();
}
}
}
OUTPUT:
