Indexers in C#

Indexers in C# allow an object to be accessed using array-like syntax. Instead of calling a method such as GetItem(0), you can write objectName[0]. This makes custom classes feel natural when they represent a collection, list, lookup table, matrix, or any structure that stores values by index or key.

An indexer is similar to a property, but it accepts one or more parameters. It uses the this keyword and has get and set accessors. The caller uses square brackets to read or assign values.

Indexers are not required in every class. They are useful when indexed access is part of the natural design of the type. If a class does not behave like a collection or lookup object, a normal method or property is usually clearer.


What Is Indexer in C#?

An indexer is a special class member that allows objects to be indexed like arrays. It provides controlled access to internal data using square bracket syntax.

class NumberStore
{
    private int[] numbers = new int[5];

    public int this[int index]
    {
        get { return numbers[index]; }
        set { numbers[index] = value; }
    }
}

The keyword this represents the current object. The parameter index is used to access the internal array. Outside code can now use the object like an array.

NumberStore store = new NumberStore();
store[0] = 10;
store[1] = 20;

Console.WriteLine(store[0]);

Indexer Syntax in C#

accessModifier returnType this[parameterType parameterName]
{
    get
    {
        return value;
    }
    set
    {
        // assign value
    }
}

The return type defines what type of value the indexer returns. The parameter defines what the caller puts inside square brackets. Inside the setter, the value keyword represents the value being assigned.

Read-Only Indexer in C#

An indexer can be read-only if it has only a get accessor. This is useful when outside code should read values by index but should not modify them.

class WeekDays
{
    private string[] days =
    {
        "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
    };

    public string this[int index]
    {
        get { return days[index]; }
    }
}

WeekDays week = new WeekDays();
Console.WriteLine(week[0]);

The caller can read week[0], but cannot assign week[0] = "Sunday" because there is no setter. This protects the internal array from external modification.

Write Access with set Accessor

If an indexer has a set accessor, outside code can assign values using square brackets. This is useful for custom collection-like objects.

class MarksStore
{
    private int[] marks = new int[3];

    public int this[int index]
    {
        get { return marks[index]; }
        set { marks[index] = value; }
    }
}

MarksStore marksStore = new MarksStore();
marksStore[0] = 85;
marksStore[1] = 90;

Console.WriteLine(marksStore[1]);

This makes MarksStore behave like a small collection. The class controls the internal array, while callers get a simple syntax.

Indexer with Validation

Indexers should validate indexes when invalid access is possible. This makes error messages clearer and prevents accidental access outside the supported range.

class SafeNumberStore
{
    private int[] numbers = new int[5];

    public int this[int index]
    {
        get
        {
            if (index < 0 || index >= numbers.Length)
            {
                throw new IndexOutOfRangeException("Invalid index");
            }

            return numbers[index];
        }
        set
        {
            if (index < 0 || index >= numbers.Length)
            {
                throw new IndexOutOfRangeException("Invalid index");
            }

            numbers[index] = value;
        }
    }
}

This indexer checks the index before reading or writing. In real code, validation is especially important when the indexer works with internal arrays, lists, buffers, or fixed-size data structures.

Indexer with String Key

An indexer does not have to use an integer index. It can use a string key, which is useful for dictionary-like access.

class Settings
{
    private Dictionary<string, string> values = new Dictionary<string, string>();

    public string this[string key]
    {
        get { return values[key]; }
        set { values[key] = value; }
    }
}

Settings settings = new Settings();
settings["Theme"] = "Dark";
settings["Language"] = "English";

Console.WriteLine(settings["Theme"]);

This style is useful when the object represents named settings, headers, configuration values, translations, or key-value data.

Indexer with Multiple Parameters

An indexer can accept more than one parameter. This is useful for matrix-like objects, grids, tables, and two-dimensional data structures.

class Matrix
{
    private int[,] data = new int[3, 3];

    public int this[int row, int column]
    {
        get { return data[row, column]; }
        set { data[row, column] = value; }
    }
}

Matrix matrix = new Matrix();
matrix[0, 1] = 50;
Console.WriteLine(matrix[0, 1]);

The caller can access values using matrix[row, column]. This is clearer than writing separate methods when indexed access is the natural operation.

Overloaded Indexers in C#

Indexers can be overloaded by changing their parameter list. A class can provide one indexer for integer access and another indexer for string access, as long as the signatures are different.

class StudentCollection
{
    private List<string> students = new List<string>
    {
        "Aarav", "Meera", "Karan"
    };

    public string this[int index]
    {
        get { return students[index]; }
    }

    public string this[string name]
    {
        get
        {
            return students.FirstOrDefault(student => student == name);
        }
    }
}

This design lets the caller access a student by numeric position or by name. Overloaded indexers should be used only when both access styles are natural and easy to understand.

Indexers vs Properties in C#

PointPropertyIndexer
Access syntaxobject.Nameobject[index]
ParametersNo parametersOne or more parameters
NameHas a property nameUses this
Best useNamed valueCollection-like access
Examplestudent.Namestudents[0]

A property is better when the value has a clear name. An indexer is better when the object naturally stores multiple values and callers need to select one value by index, key, row, column, or another parameter.

Indexers vs Methods in C#

Sometimes a method is better than an indexer. If the operation is expensive, has side effects, needs a descriptive action name, or performs a search with complex rules, a method communicates intent better.

// Indexer style: natural for direct access
string item = collection[2];

// Method style: clearer for a search operation
string item = collection.FindByName("Aarav");

Use indexers when square bracket syntax makes the code clearer. Do not use indexers only because the feature exists.

Real World Uses of Indexers

Indexers are useful when a class wraps a collection but wants to hide the exact internal storage. The class may use an array today and a list tomorrow, but callers can still use the same square bracket syntax. This supports encapsulation while keeping access simple.

  • Custom list classes that expose items by position.
  • Configuration objects that expose values by string key.
  • Matrix, table, or grid classes that use row and column indexes.
  • Cache objects that expose values by key.
  • Wrapper classes around arrays, buffers, or memory blocks.

The key idea is natural access. If users of the class instinctively think, “give me the item at this index,” an indexer may be the right design.

Common Mistakes with Indexers

  • Using an indexer when a normal property or method would be clearer.
  • Skipping index validation and allowing confusing runtime errors.
  • Putting expensive logic inside an indexer.
  • Using too many overloaded indexers in one class.
  • Returning internal mutable objects directly without considering side effects.
  • Making the class look like a collection when it is not really a collection.

The caller expects indexer access to be quick and direct. If the operation performs a slow database query, network call, or complex calculation, a method with a clear name may be better.

Best Practices for Indexers in C#

  • Use indexers only when indexed access is natural for the class.
  • Validate indexes and keys when invalid access is possible.
  • Keep indexer logic simple and predictable.
  • Use properties for named values and methods for actions.
  • Avoid hidden expensive work inside an indexer.
  • Document expected index ranges or key behavior when needed.

Indexer Interview Points

For interviews, remember that an indexer is declared using the this keyword. It can have get and set accessors like a property, but unlike a property, it accepts parameters. Indexers can be overloaded, can use different parameter types, and can accept multiple parameters.

Also remember that an indexer does not have its own name at the call site. The object name and square brackets are the interface. This is why indexers should be used carefully. If the meaning of object[key] is not obvious, a named method is usually more readable.

Indexers and Encapsulation

Indexers are not just syntax sugar. They can support encapsulation by hiding the internal storage. A class may store data in an array, list, dictionary, database-backed cache, or generated structure, while still presenting a simple indexed interface to callers.

This allows the internal implementation to change without forcing calling code to change. As long as the indexer behavior remains the same, the class can improve its storage, validation, or lookup logic internally.

Design Rule for Indexers

A good indexer should make the object easier to use, not more mysterious. If another developer sees items[2], settings["Theme"], or matrix[1, 3], the meaning should be clear without reading the whole class. When square bracket syntax communicates the model naturally, indexers make C# code compact and expressive.

If the indexed operation needs explanation every time, choose a method with a clear name instead of forcing indexer syntax; clarity comes first because square brackets hide the operation name.

FAQs on Indexers in C#

Can an indexer have multiple parameters?

Yes. An indexer can have multiple parameters, such as matrix[row, column]. This is useful for two-dimensional data structures.

Can indexers be overloaded in C#?

Yes. Indexers can be overloaded by using different parameter lists, such as one indexer with an integer parameter and another with a string parameter.

What is the difference between property and indexer?

A property is accessed by name and does not take parameters. An indexer is accessed using square brackets and takes one or more parameters.

When should I avoid indexers?

Avoid indexers when the class is not naturally collection-like, when the operation is expensive, or when a named method would explain the behavior better.


Continue learning C# in order
Follow the topic sequence with the previous and next lesson.