What are Indexers in C#?

Rupen Anjaria
4 min readAug 8, 2024

Indexers allows any custom class or struct to offer array like storage. Just like in arrays, we use indexer ( [ ] ) to access it’s element, we can have that for our custom class too. The implementation is very similar like implementing properties, except that we don’t have to provide index in property. Let’s see by an example.

Consider following scenarios. We have a class called Application. An application can have references and it should not be more than 3. Let’s see the implementation:

using System;

class Reference<T>
{
private T[] _refearr = new T[3];
private int nextIndex = 0;

//This is indexer
public T this[int i]
{
get => _refearr[i];
set
{
if (nextIndex >= _refearr.Length)
throw new IndexOutOfRangeException($"You can only provide {_refearr.Length} references.");
_refearr[nextIndex++] = value;
}
}
}

class Application
{
public string Name { get; set; }
public Reference<string> AppReference { get; set; }

public Application()
{
AppReference = new Reference<string>();
}
}

class Program
{
static void Main()
{
var myApplication = new Application();
myApplication.Name = "Member Application";
myApplication.AppReference[0] = "A123";
myApplication.AppReference[1] = "B123";
myApplication.AppReference[2] = "C123";

Console.WriteLine(myApplication.AppReference[0]); // Output: A123
}
}

See the Reference class. It has built in limit of 3 members and we have defined array using private T[] _refearr = new T[3]; . The indexer is defined as public T this[int i]. It also is a generic class with simple get and set accessor methods to assign and retrieve values. The generic will make Reference more flexible for other requirements, like storing int, float etc.

On set, we are checking the max number of elements and throwing exception accordingly. This is very important to handle the scenario where a user can accidentally create four or more references. As is, indexer set must have some kind of validation as it’s based on array.

Further, you can’t have two indexers in a same class of same type. Meaning, if you modify Reference class as below then it will throw an error.

class Reference<T>
{
private T[] _refearr = new T[3];
private int nextIndex = 0;

//This is indexer
public T this[int i]
{
get => _refearr[i];
set
{
if (nextIndex >= _refearr.Length)
throw new IndexOutOfRangeException($"You can only provide {_refearr.Length} references.");
_refearr[nextIndex++] = value;
}
}

//This is second indexer
public T this[int i]
{
/* get and set implementation */
}
}

C# compiler will throw an error for above code as both the indexers have same signature. To resolve this issue, we can defined it like this:

class Reference<T>
{
private T[] _refearr = new T[3];
private int nextIndex = 0;

//This is indexer
public T this[int i]
{
get => _refearr[i];
set
{
if (nextIndex >= _refearr.Length)
throw new IndexOutOfRangeException($"You can only provide {_refearr.Length} references.");
_refearr[nextIndex++] = value;
}
}

//This is second Valid indexer
public T this[string i]
{
/* get and set implementation */
}
}

Indexer in Interface

We can also declare indexer in an interface. As we know, interface itself doesn’t have any implementation and is merely a template, indexer will help users to define their own version of the indexer. Let’s look at an example.

using System;
using System.Collections.Generic;

public interface IPhoneBook
{
string this[string name] { get; set; } // This is indexer in interface
void AddContact(string name, string phoneNumber);
}
public class PhoneBook : IPhoneBook
{
private readonly Dictionary<string, string> _contacts = new Dictionary<string, string>();

public string this[string name]
{
get => _contacts.ContainsKey(name) ? _contacts[name] : "Contact not found";
set => _contacts[name] = value;
}

public void AddContact(string name, string phoneNumber)
{
_contacts[name] = phoneNumber;
}

}

class Program
{
static void Main()
{
IPhoneBook phoneBook = new PhoneBook();

// Adding contacts
phoneBook.AddContact("John Doe", "555-1234");
phoneBook.AddContact("Jane Smith", "555-5678");

// Accessing contacts using the indexer
Console.WriteLine(phoneBook["John Doe"]); // Output: 555-1234
Console.WriteLine(phoneBook["Jane Smith"]); // Output: 555-5678
Console.WriteLine(phoneBook["Unknown"]); // Output: Contact not found

// Updating a contact using the indexer
phoneBook["John Doe"] = "555-0000";
Console.WriteLine(phoneBook["John Doe"]); // Output: 555-0000


}
}

Output:

555-1234
555-5678
Contact not found
555-0000

Here, we have developed a phone book application where users can look up contact details by name. We want to create an interface that enforces any class implementing it to support indexing by contact name.

As you can see, we haven’t created any GetContact methods, this is because, indexer will help us to retrieve it.

Conclusion

Indexers allow objects to be accessed like arrays, where a get accessor retrieves a value, and a set accessor assigns one. The indexing mechanism isn’t limited to integers—you can define it however you like. Indexers can be overloaded and can also accept multiple parameters, such as when working with a two-dimensional array.

--

--

No responses yet