We have now come to the part in the C# in Simple Terms series where we can explore some cool but little-used C# features. Among these is the ability to access values in a class instance in the same way we access array values; we do this using a C# feature called indexers.

So, let's build some indexers!

A checklist of items, written in a notebook, with a hand holding a pen about to check one of the boxes.
Check[1] = true; Photo by Glenn Carstens-Peters / Unsplash

The Sample Project

exceptionnotfound/CSharpInSimpleTerms
Contribute to exceptionnotfound/CSharpInSimpleTerms development by creating an account on GitHub.
Project for this post: 21Indexers

Indexers

Indexers allow C# class instances to be indexed like arrays. This means we can access a particular value without explicitly specifying the type of said value.

This is particularly useful when defining custom collection classes. Let's say we create a custom collection class called IntMathCollection which keeps the elements of the collection in an array:

public class IntMathCollection
{
    private int[] _values = new int[1000];
}

For simplicity, let's populate the collection of _values with randomly-generated integers 1-10000.

public class IntMathCollection
{
    private int[] _values = new int[1000];

    public IntMathCollection()
    {
        Random rand = new Random(DateTime.Now.Millisecond);
        
        for(int i = 0; i< 1000; i ++)
        {
            _values[i] = rand.Next(1,10000);
        }
    }
}

Setup

The reason we might have such a class is to find statistics about the collection of integers, such as the mean, median, and mode. Let's implement some methods to find these statistics.

The mean is a simple average:

public class IntMathCollection
{
    //...Other methods and properties

    public double Mean()
    {
        return _values.ToList().Average();
    }
}

The median is the number that appears in the middle of an ordered list of values. Since this array will always have an even number of values, we return the average of the two numbers in the middle of the ordered array.

public class IntMathCollection
{
    //...Other methods and properties
    
    public int Median()
    {
        var orderedValues = _values.ToList().OrderBy(x => x).ToArray();
        var value1 = orderedValues[499];
        var value2 = orderedValues[500];

        return (value1 + value2) / 2;
    }
}

Finally, the mode is the single value that appears the most number of times in the collection. We can group the values and then order them using LINQ to find the mode.

public class IntMathCollection
{
    //...Other methods and properties
    
    public int Mode()
    {
        return _values.GroupBy(v => v)
                      .OrderByDescending(g => g.Count())
                      .First()
                      .Key;
    }
}

The implementation of these methods provides a reason for this class to exist separately of another, more generic collection like List<T>.

Creating an Indexer on a Class

If we wanted to access a specific value in the IntMathCollection instance, we are currently unable to, since the class does not define a way to do this. We can create an indexer using the this keyword, and access individual values using the value keyword.

public class IntMathCollection
{
    public int this[int i]
    {
        get { return _values[i]; }
        set { _values[i] = value; }
    }
    
    //...Other methods and properties
}

Note that an indexer is really just a special property of the class, complete with get and set accessors.

We can use this new indexer like so:

IntMathCollection myInts = new IntMathCollection();
Console.WriteLine("Element at position 67 is " + myInts[67]);
Console.WriteLine("Element at position 813 is " + myInts[813]);
Console.WriteLine("Element at position 490 is " + myInts[490]);

Using Expression Body Members

Since C# 7.0, we have been able to implement set accessors using the expression body member syntax (we could implement get accessors using this syntax before C# 7.0). Our indexer can now be simplified to the following:

public class IntMathCollection
{
    public int this[int i]
    {
        get => _values[i];
        set => _values[i] = value;
    }
    
    //...Other methods and properties
}

Creating an Indexer on an Interface

We can also create an indexer on an interface using a slightly different syntax.

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

We discussed interfaces in a previous post in this series:

C# in Simple Terms - Interfaces and Abstract Classes
Let’s inherit behavior and properties using two related, but different, kinds of C# objects.

Classes which implement this interface must then provide the implementation for the indexer.

public class HasIndexer : IHasIndexer
{
    private string[] _values = new string[10];

    public string this[int index]
    {
        get => _values[index];
        set => _values[index] = value;
    }
}

Other Details

It is also possible to do the following:

Check out the official Microsoft documentation for more.

Indexers - C# Programming Guide
Indexers in C# allow class or struct instances to be indexed like arrays. You can set or get the indexed value without specifying a type or instance member.

New Keyword Usage

  • this - Used in indexers to create an indexer.
  • value - Used in set accessors to access a given value.

Summary

Indexers in C# allow class instances to be accessed like arrays. We create an indexer using the this keyword and get the value being given with the value keywords. We can implement indexers on both classes and interfaces, with interfaces needing a slightly different syntax.

Got questions about indexers? I'm here to help! Ask away in the comments below.

In the next part of this series, we will discuss another C# feature we missed the first time around but that you probably will use pretty darn often: iterators.

Happy Coding!