C# Indexers

An indexer lets you access elements of a class using square bracket notation — just like accessing elements in an array. It makes your custom classes behave like built-in collections, improving readability and usability.

What Is an Indexer?

┌──────────────────────────────────────────────────────────────┐
│  Without indexer:                                            │
│  Console.WriteLine(classroom.GetStudent(2));                 │
│                                                              │
│  With indexer:                                               │
│  Console.WriteLine(classroom[2]);   ← array-like syntax      │
│                                                              │
│  Built-in types that use indexers:                           │
│  string s = "hello";  char c = s[1];   → 'e'                 │
│  List<T> list;        int x = list[0]; → first element       │
│  Dictionary d;        d["key"] = val;  → key-value access    │
└──────────────────────────────────────────────────────────────┘

Indexer Syntax

Use this[type index] to define an indexer. It uses get and set accessors just like a property.

class Classroom
{
    private string[] students = new string[30];

    // Indexer definition:
    public string this[int index]
    {
        get { return students[index]; }
        set { students[index] = value; }
    }
}

class Program
{
    static void Main()
    {
        Classroom room = new Classroom();

        room[0] = "Alice";   // calls set accessor
        room[1] = "Bob";
        room[2] = "Carol";

        Console.WriteLine(room[0]);  // calls get accessor → Alice
        Console.WriteLine(room[2]);  // Carol
    }
}

Indexer Anatomy Diagram

┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  public  string  this[int index]                             │
│  │       │       │    │                                      │
│  │       │       │    └── index parameter (can be any type)  │
│  │       │       └── 'this' = indexer keyword                │
│  │       └── return type                                     │
│  └── access modifier                                         │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Indexer with Bounds Checking

class SafeArray
{
    private int[] data;

    public SafeArray(int size)
    {
        data = new int[size];
    }

    public int this[int index]
    {
        get
        {
            if (index < 0 || index >= data.Length)
                throw new IndexOutOfRangeException("Index " + index + " is out of range.");
            return data[index];
        }
        set
        {
            if (index < 0 || index >= data.Length)
                throw new IndexOutOfRangeException("Index " + index + " is out of range.");
            data[index] = value;
        }
    }

    public int Length => data.Length;
}

class Program
{
    static void Main()
    {
        SafeArray arr = new SafeArray(5);
        arr[0] = 10;
        arr[2] = 30;

        Console.WriteLine(arr[0]);   // 10
        Console.WriteLine(arr[2]);   // 30

        // arr[10] = 99;   // throws IndexOutOfRangeException
    }
}

String Indexer (Non-Integer Index)

Indexers are not limited to int. A string index works like a dictionary.

class PhoneBook
{
    private Dictionary<string, string> contacts = new Dictionary<string, string>();

    public string this[string name]
    {
        get
        {
            return contacts.ContainsKey(name) ? contacts[name] : "Not found";
        }
        set
        {
            contacts[name] = value;
        }
    }
}

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

        book["Alice"] = "555-1234";
        book["Bob"]   = "555-5678";

        Console.WriteLine(book["Alice"]);   // 555-1234
        Console.WriteLine(book["Bob"]);     // 555-5678
        Console.WriteLine(book["Carol"]);   // Not found
    }
}

Multi-Dimensional Indexer

Indexers can take multiple parameters — perfect for grid-like structures such as matrices or game boards.

class Grid
{
    private int[,] cells;
    int rows, cols;

    public Grid(int rows, int cols)
    {
        this.rows  = rows;
        this.cols  = cols;
        cells = new int[rows, cols];
    }

    public int this[int row, int col]
    {
        get { return cells[row, col]; }
        set { cells[row, col] = value; }
    }
}

class Program
{
    static void Main()
    {
        Grid board = new Grid(3, 3);

        board[0, 0] = 1;
        board[1, 1] = 5;
        board[2, 2] = 9;

        Console.WriteLine(board[1, 1]);   // 5

        // Display grid:
        for (int r = 0; r < 3; r++)
        {
            for (int c = 0; c < 3; c++)
                Console.Write(board[r, c] + " ");
            Console.WriteLine();
        }
        // 1 0 0
        // 0 5 0
        // 0 0 9
    }
}

Multi-Index Diagram

┌──────────────────────────────────────────────────────────────┐
│  board[1, 2] = 7;                                            │
│         │  │                                                 │
│         │  └── col = 2                                       │
│         └── row = 1                                          │
│                                                              │
│  Grid:                                                       │
│       Col0  Col1  Col2                                       │
│  Row0 [  1] [  0] [  0]                                      │
│  Row1 [  0] [  5] [  7]  ← board[1,2] = 7                    │
│  Row2 [  0] [  0] [  9]                                      │
└──────────────────────────────────────────────────────────────┘

Overloading Indexers

A class can have multiple indexers with different parameter types — this is called indexer overloading.

class DataStore
{
    private string[] names  = { "Alice", "Bob", "Carol" };
    private int[]    scores = { 90, 85, 92 };

    // Access by integer index:
    public string this[int index]
    {
        get { return names[index]; }
    }

    // Access by string name — returns score:
    public int this[string name]
    {
        get
        {
            for (int i = 0; i < names.Length; i++)
                if (names[i] == name) return scores[i];
            return -1;
        }
    }
}

class Program
{
    static void Main()
    {
        DataStore store = new DataStore();

        Console.WriteLine(store[0]);        // Alice  (int indexer)
        Console.WriteLine(store["Bob"]);    // 85     (string indexer)
        Console.WriteLine(store["Carol"]);  // 92
    }
}

Read-Only Indexer

Omit the set accessor to create a read-only indexer.

class FibonacciSequence
{
    public int this[int n]
    {
        get
        {
            if (n <= 1) return n;
            int a = 0, b = 1;
            for (int i = 2; i <= n; i++)
            {
                int temp = a + b;
                a = b;
                b = temp;
            }
            return b;
        }
    }
    // No 'set' → read-only indexer
}

class Program
{
    static void Main()
    {
        FibonacciSequence fib = new FibonacciSequence();
        for (int i = 0; i <= 7; i++)
            Console.Write(fib[i] + " ");
        // 0 1 1 2 3 5 8 13
    }
}

Indexer vs Property vs Method

┌───────────────────────────────────────────────────────────────┐
│  Property:   obj.Name              → single named value       │
│  Indexer:    obj[key]              → value from a collection  │
│  Method:     obj.Get(key)          → same, but explicit call  │
│                                                               │
│  Use indexer when your class logically represents a           │
│  collection and bracket access feels natural to the caller.   │
└───────────────────────────────────────────────────────────────┘

Quick Summary

┌─────────────────────────────────────────────────────────────┐
│  public T this[int i] { get { } set { } }  → basic indexer  │
│  public T this[string k] { get { } set { }}→ string key     │
│  public T this[int r, int c] { ... }       → 2D indexer     │
│                                                             │
│  Rules:                                                     │
│  • Defined with 'this' keyword                              │
│  • Can have any number/type of parameters                   │
│  • Can be overloaded (multiple parameter types)             │
│  • get and/or set — make read-only by omitting set          │
└─────────────────────────────────────────────────────────────┘

Indexers give your custom classes the familiar, intuitive feel of built-in collections. When your class manages a group of items — a grid, a cache, a book of contacts — an indexer makes accessing those items feel completely natural to anyone using your code.

Leave a Comment

Your email address will not be published. Required fields are marked *