Telerik blogs
DotNetT Light_870x220

Indexers allow instances of a class to be indexed just like arrays, and are declared similarly to methods. They can be useful when working with your own collection class. Let's learn how to define and work with indexers.

You may encounter a situation where you want to access data in your custom types like an array, using the index operator, []. This feature can be useful when creating custom generic classes. An indexer allows an object to be indexed, such as an array, and the indexed value can be set or retrieved without explicitly specifying a type or instance member.

To declare an indexer for a class, you add a slightly different property with the this[] keyword and arguments of any type between the brackets. Properties return or set a specific data member, whereas indexers return or set a particular value from the object instance.

Let's take a look at an example to see how to implement an indexer.

class User
{
  public string Name { get; set; }
  public string Email { get; set; }
}

class UserCollection
{
  ArrayList users = new ArrayList();

  public User this[int index]
  {
    get => (User) users[index];
    set => users.Insert(index, value);
  }

  public int Count => users.Count;
}

Above we defined an indexer whose argument is an int type. We have used the ArrayList class to hold a collection of User objects and made use of its indexer to retrieve and store users based on the passed value. Here's how to use the indexer:

var users = new UserCollection();

// add objects using indexer
users[0] = new User("Julie Lerman", "joelin@indo.com");
users[1] = new User("Mark Lettuce", "mark@lettuce.com");
users[2] = new User("Peter Mbanugo", "p.mbanugo@yahoo.com");

// obtain and display each item using indexer
for (int i = 0; i < users.Count; i++)
{
  Console.WriteLine("User number: {0}", i);
  Console.WriteLine("Name: {0}", users[i].Name);
  Console.WriteLine("Email: {0}", users[i].Email);
  Console.WriteLine();
}

// output
// User number: 0
// Name: Julie Lerman
// Email: joelin@indo.com
// User number: 1
// Name: Mark Lettuce
// Email: mark@lettuce.com
// User number: 2
// Name: Peter Mbanugo
// Email: p.mbanugo@yahoo.com

As you can see from the example above, using the indexer is similar to how you've already been using indexers in .NET.

Indexing Using String

We used integers for indexing in the previous example, but you can also use any other type as argument for the indexer. Let's update the implementation of UserCollection to use a string argument type for the indexer method.

class UserCollection
{
  Dictionary<string, User> users = new Dictionary<string, User>();

  public User this[string name]
  {
    get => (User) users[name];
    set => users[name] = value;
  }
}

// using the indexer
static void Main(string[] args)
{
  var users = new UserCollection();

  // add objects using indexer
  users["julie"] = new User("Julie Lerman", "joelin@indo.com");
  users["mark"] = new User("Mark Lettuce", "mark@lettuce.com");
  users["peter"] = new User("Peter Mbanugo", "p.mbanugo@yahoo.com");

  // obtain and display Mark's data
  Console.WriteLine($"Marks Email: {users["mark"].Email}");
  Console.Read();
}

The implementation now uses a dictionary to hold the list of users because it allows us to store data with keys of any type. With that, you can add and retrieve user objects with a string value when using the indexer. When you run the code, it should output Mark's Email: mark@lettuce.com to the console.

Overloading Indexers

Indexer methods can also be overloaded. If you find yourself in a situation where you'd like to access items using a numerical value or string value, you can define multiple indexer methods for that type, thereby having overloaded indexers. Following on with our example from the previous section, let's update the UserCollection to include a numerical indexer and another that accepts string value type:

class UserCollection
{
  Dictionary<string, User> users = new Dictionary<string, User>();

  public User this[string name]
  {
    get => (User) users[name];
    set => users[name] = value;
  }

  public User this[int key]
  {
    get => (User) users[key.ToString()];
    set => users[key.ToString()] = value;
  }

  public int Count => users.Count;
}

The indexer methods for the UserCollection has two overloads: one that takes integer and another that takes a string value. You can have as many overloads as you'd like, just like you would for methods you define in your classes. The code below shows an example usage of both indexers defined in UserCollection.

var users = new UserCollection();

// add objects using indexer
users["julie"] = new User("Julie Lerman", "joelin@indo.com");
users["mark"] = new User("Mark Lettuce", "mark@lettuce.com");
users[3] = new User("Peter Mbanugo", "p.mbanugo@yahoo.com");
users[3] = new User("Peter Liko", "liko@jordan.com");

Console.WriteLine($"{users[3].Name} - {users[3].Email}");
Console.Read();

// output
// Peter Liko - liko@jordan.com

You should notice from the code above that you can use either string or integer as parameter types to the indexer methods. It showed two assignments using the integer value, 3. The second assignment replaces the initial assignment, and calling the get accessor will return the last value set for users[3], which should be Peter Liko - liko@jordan.com.

That's a Wrap!

Indexers are similar to properties, but are accessed via an index argument rather than a property name. You define them similarly to how you would define properties but using this[] syntax. I showed you how to declare it with examples of how to have overloaded indexer methods and how to use them.


Peter Mbanugo
About the Author

Peter Mbanugo

Peter is a software consultant, technical trainer and OSS contributor/maintainer with excellent interpersonal and motivational abilities to develop collaborative relationships among high-functioning teams. He focuses on cloud-native architectures, serverless, continuous deployment/delivery, and developer experience. You can follow him on Twitter.

Comments

Comments are disabled in preview mode.