Daily Knowledge Drop
Previously we have look at a method to add an indexer and access class as an array. Today we explore indexers again, and how custom indexers can be written which accept not only integers, but other types as well as multiple parameters
, to access data in a variety of ways.
List example
First as a benchmark, we'll have a look at the List
class:
var strList = new List<string>();
strList.Add("one");
strList.Add("two");
strList.Add("three");
strList.Add("four");
strList.Add("five");
Console.WriteLine(strList[3]);
The items added to the List can be accessed using an int indexer. In the above example strList[3] will return the 4th item in the list. This is standard built-in functionality.
EnhancedList example
Next we'll create our own EnhancedList
, which inherits from the List
class, but provides additional functionality through custom indexers.
The base EnhancedList
looks as follows and operates exactly the same as a normal List
:
public class EnhancedList<T> : List<T> { }
Access index
First, let's create an indexer to get the index, based on the value
. This is basically exactly what the IndexOf
method does, but as an indexer:
public class EnhancedList<T> : List<T>
{
public int this[T value] => this.Contains(value) ? this.IndexOf(value) : -1;
}
The method checks if the list contains the value passed in, and if it does will return the value's index, otherwise -1 will be returned.
The usage is now as follows:
var enhancedList = new EnhancedList<string>();
enhancedList.Add("one");
enhancedList.Add("two");
enhancedList.Add("three");
enhancedList.Add("four");
enhancedList.Add("five");
// access the value based on the index
Console.WriteLine(enhancedList[3]);
// access the index based on the value
Console.WriteLine(enhancedList["two"]);
The output of the above is:
four
1
The built in indexer for List
accepts an int as a parameter, and we've created an indexer which accepts type T (the type contained in the EnhancedList), in this example, a string.
Multiple index lookup
As we've seen, EnhancedList[index]
can be used to get the value at the specified index. Let's update the EnhancedList
to accept multiple indexes and return multiple values
:
public class EnhancedList<T> : List<T>
{
public int this[T value] => this.Contains(value) ? this.IndexOf(value) : -1;
public IEnumerable<T> this[bool rangeLookup, params int[] indexes] =>
indexes.Select(i => (T)this[i]);
}
A new indexer has been added, this time taking a bool and an array of integers as arguments. The bool parameter is required to differentiate between EnhancedList[index] and EnhancedList[params] - without the bool forcing a difference, there is no way of specifying which indexer is being called.
The usage is now as follows:
var enhancedList = new EnhancedList<string>();
enhancedList.Add("one");
enhancedList.Add("two");
enhancedList.Add("three");
enhancedList.Add("four");
enhancedList.Add("five");
// get the value for index 2 and 4
foreach (var lookupItem in enhancedList[true, 2, 4])
{
Console.WriteLine(lookupItem);
}
In the above, we get the values at index 2 and 4. The output being:
three
five
T modification
The generic type T contained in the EnhancedList
can also be modified before being returned by the indexer. In the last example, we are going to create an indexer which returned the items in the list as a string, ready for output to the Console:
public class EnhancedList<T> : List<T>
{
public int this[T value] => this.Contains(value) ? this.IndexOf(value) : -1;
public IEnumerable<T> this[bool rangeLookup, params int[] indexes] =>
indexes.Select(i => (T)this[i]);
public IEnumerable<string> this[string prefixMessage, params int[] indexes] =>
indexes.Select(i => $"{prefixMessage} {(T)this[i]}");
}
The new indexer takes a string prefix message, and an array of indexes. Instead of returning just the values at the position of the indexes (as in the previous example), now the prefixMessage and the value are combined before being returned.
The usage is now as follows:
var enhancedList = new EnhancedList<string>();
enhancedList.Add("one");
enhancedList.Add("two");
enhancedList.Add("three");
enhancedList.Add("four");
enhancedList.Add("five");
// return value at index 0, 2 and 4
// with the supplied message
foreach (var lookupItem in enhancedList["Printing item ...", 0, 2, 4])
{
Console.WriteLine(lookupItem);
}
In the above, we get the values at index 2 and 4. The output being:
Printing item ... one
Printing item ... three
Printing item ... five
Notes
While the examples shown above are not necessarily production ready or practical, they do show how indexers can be created which accept multiple arguments, allowing for some innovative possibilities depending on your specific use case.
References
Daily Drop 95: 14-06-2022
At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.
The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.