This article is part of the Things you might not know about CSharp series. You are reading part 1 of 2.

This is the first part of my series taking a look at some of the lesser known features of the C# language. Today we will be looking at index parameters, specifically how to use params with them, and how they are used in C#. With these articles, your millage may vary in terms of usability of these features for day to day programming, so think of this series as raising awareness that these things exist, not that you should use for general purpose programming. Without further adieu…

What is an indexer

An indexer is a piece of syntax sugar for C# that allows a given class/interface/struct the ability to access data like you would do with an array. You can provide a getter, a setter or both and set the accessibility of the getter/setters. In essence, an indexer acts like a parameterised property.

Below is a contrived example of how you would create an indexer for a class and then use it:

public class IndexerExample {
    
    private Dictionary<String, String> _data = new();
    
    public String this[string id]
    {
         get => _data[id];
    }
}

public class TestClass
{
    private readonly IndexerExample _instance = new();
    
    public string TestMethod() => _instance["hello"];
}

Play with this example on sharplab.io

For more information on the basic usage of indexers, check the documentation here.

Passing multiple parameters

Whilst indexers are a relatively well-known concept, mainly from their usage in lists and arrays, it’s not very common knowledge that you can pass multiple parameters to an indexer, like this example:

public class IndexerExample {
    
    private Dictionary<String, String> _data = new();
    
    public String this[params string[] ids]
    {
         get => string.Join(", ", ids.Select(x => _data[x]));
    }
}

public class TestClass
{
    private readonly IndexerExample _instance = new();
    
    public string TestMethod() => _instance["hello", "world"];
}

Play with this example on sharplab.io

In this example, we can specify many arguments into the indexer, and have one or many results returned. As with other members in C#, so long as the signature is different, we can overload indexers with different variations, as shown in the example below:

public class IndexerExample {
    
    private Dictionary<String, String> _data = new();
    
    public String this[string id]
    {
         get => _data[id];
    }
    
    public IEnumerable<String> this[params string[] ids]
    {
         get => ids.Select(x => _data[x]);
    }
}

public class TestClass
{
    private readonly IndexerExample _instance = new();

    public String ReturnSingle() => _instance["hello"];

    public IEnumerable<String> ReturnMany() => _instance["hello", "world"];
}

Play with this example on sharplab.io

And that’s it for this article, join me next time where we are going to learn about duck typing in C# and different use cases for it. I hope that you’ve learnt something new and have another tool in your toolbox.