How to use indexers in C#

By FoxLearn 1/7/2025 2:30:16 AM   51
In C#, indexers allow objects to be accessed like arrays, enabling you to use an object with index values.

They are similar to properties but differ in that their accessors take parameters. While properties are accessed by name, indexers require index values.

Additionally, indexers are instance members of a class and cannot be static, whereas properties can be either static or non-static.

For example, how to declare an indexer in C#:

<Modifier> <Return type> this[<argument list>]
{
    get
    {
    }
    set
    {
    }
}

The Modifier in the syntax can be private, public, protected, or internal.

Consider the following example with a Product class:

public class Product
{
    private string[] details = new string[3];

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

Here, the Product class defines an indexer to access elements from a private details array. You can use the indexer as follows:

Product product = new Product();
product[0] = "Laptop";
product[1] = "16GB RAM";
product[2] = "1TB SSD";

for (int i = 0; i < 3; i++)
{
    Console.WriteLine(product[i]);
}

In this example, we created an instance of Product, assigned values to the details array using the indexer, and printed them out.

It’s important to note that the this keyword is used when defining an indexer. Also, indexers are not limited to using integers as indices; other lookup mechanisms can be used as well.

For example, consider an example where a Customer class uses an indexer to access orders by their OrderID:

public class Customer
{
    public List<Order> Orders { get; set; }

    public Order this[int orderID]
    {
        get
        {
            return (from o in Orders
                    where o.OrderID == orderID
                    select o).First();
        }
    }
}

public class Order
{
    public int OrderID { get; set; }
}

Here, the Customer class defines an indexer that retrieves an order from a list of Orders based on the OrderID.

Below is how you can create a Customer instance, add some Order objects to the Orders list, and access an order using the indexer:

List<Order> orderList = new List<Order>
{
    new Order { OrderID = 1 },
    new Order { OrderID = 2 }
};

Customer customer = new Customer { Orders = orderList };
Order order = customer[1];  // Access the order with OrderID = 1

In this case, you use the OrderID as the index to retrieve the corresponding order.

Indexers with Inheritance and Polymorphism

Indexers can support inheritance, polymorphism, and even be abstract.

Let’s look at an example where we modify the ContactBase class to make the indexer virtual:

public class ContactBase
{
    protected string[] address = new string[3];

    public virtual string this[int index]
    {
        get
        {
            return address[index];
        }
        set
        {
            address[index] = value;
        }
    }
}

You can then inherit from ContactBase and override the indexer:

public class ConcreteContact : ContactBase
{
    public override string this[int index]
    {
        get
        {
            return address[index];
        }
        set
        {
            address[index] = value;
        }
    }
}

This example shows how an indexer can be overridden in a derived class, supporting polymorphic behavior.

Abstract Indexers

You can also define an indexer as abstract in an abstract class. Here’s how the ContactBase class would look with an abstract indexer:

public abstract class ContactBase
{
    protected string[] address = new string[3];

    public abstract string this[int index]
    {
        get; set;
    }
}

Even though the indexer is abstract, you can still implement it in a derived class.

Here's how the ConcreteContact class implements the abstract indexer:

public class ConcreteContact : ContactBase
{
    public override string this[int index]
    {
        get
        {
            return address[index];
        }
        set
        {
            address[index] = value;
        }
    }
}

Finally, you can assign values to an instance of ConcreteContact using the indexer as follows:

ConcreteContact contact = new ConcreteContact();
contact[0] = "New York";
contact[1] = "California";
contact[2] = "Texas";