C# Indexer

This article will explore the amazing C# feature called indexer. It will discuss the following questions: what is indexer, the difference between properties and indexers, sample codes and many more.

Introduction

 
The first impression of most developers I’ve encountered when they experienced indexers is, they felt like it was an array. It is because instances of a class or struct which uses indexers are behaving like an array. However; don’t be confused --  indexers aren’t arrays, but they appear to be. Let us try to answer, what is an indexer? In the next section.
 

What is an Indexer?

 
Indexers are like properties which accept arguments and indexer object can be indexed in the same way an array does. In addition, because it is like properties it can have access modifiers such as public, private, protected or internal. Then last but not least, we can’t ignore the getters and setters of course. The purpose of the indexer is basically to simply the syntax (syntactic sugar).
 

The difference between Properties and Indexers

 
Properties Indexers
An instance or static member Instance member only
Accessed via a name Accessed via an index
Getters has no parameters Getters has the same formal parameter list as the indexer
Setters uses implicit value parameter. Setters of an index have the same formal parameter list of the indexer and to the value parameter.
Supports auto-implemented properties Doesn’t support auto implemented properties.
 

When to use Indexer?

  • If you need a collection of its instances.
  • If you need a collection of values directly related to your instance.
Examples
  • If you need a collection of its instances.
    1. using System;  
    2. using System.Linq;  
    3.   
    4. namespace CSharpIndexFeatureExplore {  
    5.     public enum Title {  
    6.         MR = 0,  
    7.         MS = 1  
    8.     }  
    9.   
    10.     public class Customer {  
    11.         public Customer () : this (5) { }  
    12.   
    13.         public Customer (int lengthOfCustomers) {  
    14.             this._customers = new Customer[lengthOfCustomers];  
    15.         }  
    16.         public Title Title { getset; }  
    17.         public string TitleInString {  
    18.             get {  
    19.                 string title = Title.ToString ();  
    20.   
    21.                 return new string (title.Select ((ch, index) => (index == 0) ? ch : Char.ToLower (ch)).ToArray ());  
    22.             }  
    23.         }  
    24.         public string FirstName { getset; }  
    25.   
    26.         public string LastName { getset; }  
    27.   
    28.         public int CustomersLength { get { return this._customers.Length; } }  
    29.   
    30.         private readonly Customer[] _customers;  
    31.         public Customer this [int index] {  
    32.             get { return this._customers[index]; }  
    33.             set { this._customers[index] = value; }  
    34.         }  
    35.         public override string ToString () => $"{this.TitleInString}. {this.LastName}, {this.FirstName}";  
    36.     }  
    37. }  
    Now let us try to make a unit-test so could see how indexers work. 
    1. using System;  
    2. using CSharpIndexFeatureExplore;  
    3. using Microsoft.VisualStudio.TestTools.UnitTesting;  
    4.   
    5. namespace UnitTestProject1 {  
    6.   
    7.     [TestClass]  
    8.     public class IndexFeatureInstanceList {  
    9.   
    10.         private Customer _customer;  
    11.   
    12.         [TestInitialize ()]  
    13.         public void TestInit () {  
    14.             this._customer = new Customer (5) { };  
    15.   
    16.             this.Initialize_Customer_Collection ();  
    17.         }  
    18.   
    19.         private void Initialize_Customer_Collection () {  
    20.             this._customer[0] = new Customer { Title = Title.MR, FirstName = "Red", LastName = "Ford" };  
    21.             this._customer[1] = new Customer { Title = Title.MS, FirstName = "Anne", LastName = "Green" };  
    22.             this._customer[2] = new Customer { Title = Title.MR, FirstName = "Jack", LastName = "Robinsons" };  
    23.             this._customer[3] = new Customer { Title = Title.MS, FirstName = "Michelle", LastName = "Miguelito" };  
    24.             this._customer[4] = new Customer { Title = Title.MS, FirstName = "Trix", LastName = "Delacruz" };  
    25.         }  
    26.   
    27.         [TestMethod]  
    28.         [Priority (1)]  
    29.         public void Test_Create_Instances_Of_Customer_Class () {  
    30.   
    31.             Assert.IsNotNull (_customer);  
    32.             Assert.IsTrue (_customer.CustomersLength > 0);  
    33.   
    34.         }  
    35.   
    36.         [TestMethod]  
    37.         [Priority (2)]  
    38.         public void Test_Loop_Throught_Customer_Instances () {  
    39.             int length = this._customer.CustomersLength;  
    40.   
    41.             for (int i = 0; i < length; i++) {  
    42.                 Console.WriteLine (this._customer[i].ToString ());  
    43.             }  
    44.         }  
    45.     }  
    46. }   
  • If you need a collection of values directly related to your instance.
    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Linq;  
    4. using System.Runtime.CompilerServices;  
    5.   
    6. namespace CSharpIndexFeatureExplore {  
    7.     public enum FoodType {  
    8.         JUNK = 0,  
    9.         GO = 1,  
    10.         GROW = 2  
    11.     }  
    12.   
    13.     public class Food {  
    14.         private Dictionary<FoodType, string[]> foodCollection;  
    15.   
    16.         public Food () {  
    17.             foodCollection = new Dictionary<FoodType, string[]> { { FoodType.JUNK, new string[] { "Potato Chips""Sweet Potato Chips" } },  
    18.                 { FoodType.GO, new string[] { "Sweet Potatoes""Rice""Pasta""Bread" } },  
    19.                 { FoodType.GROW, new string[] { "Chicken""Eggs""Fish""Meat" } }  
    20.             };  
    21.         }  
    22.   
    23.         public int FruitsLength { getset; }  
    24.   
    25.         [IndexerName ("FoodPyramid")]  
    26.         public string[] this [FoodType index, string foodType = ""] {  
    27.             get {  
    28.                 return foodCollection[index];  
    29.             }  
    30.   
    31.             set {  
    32.                 if (!Enum.GetNames (typeof (FoodType)).Any (f => f == foodType)) {  
    33.                     throw new ApplicationException ("Food type not available");  
    34.                 }  
    35.   
    36.                 foodCollection[index] = value;  
    37.             }  
    38.         }  
    39.     }  
    40. }  
    Before we go to the unit-test. Let us try to see if the "IndexerName" attribute would work with other programming language. In this scenario, we have used the VB programming language. 
    1. Imports CSharpIndexFeatureExplore  
    2.   
    3. Module Module1  
    4.   
    5.     Sub Main()  
    6.         Dim fruits As New Food()  
    7.         Dim myCollectionOfFruits = fruits.FoodPyramid(1)  
    8.   
    9.         For Each item As String In myCollectionOfFruits  
    10.             Console.WriteLine(item)  
    11.         Next  
    12.   
    13.     End Sub  
    14.   
    15. End Module   
    Again, let us try to make a unit-test so could see how indexers work in this scenario.  
    1. using System;  
    2. using System.Collections.Generic;  
    3. using CSharpIndexFeatureExplore;  
    4. using Microsoft.VisualStudio.TestTools.UnitTesting;  
    5.   
    6. namespace UnitTestProject1 {  
    7.   
    8.     [TestClass]  
    9.     public class IndexFeaturesRepresentsList {  
    10.   
    11.         private Food _food;  
    12.   
    13.         [TestInitialize ()]  
    14.         public void Init () {  
    15.             this._food = new Food ();  
    16.         }  
    17.   
    18.         [TestMethod]  
    19.         public void Test_Get_FoodPyramid () {  
    20.             var foodTypes = (FoodType[]) Enum.GetValues (typeof (FoodType));  
    21.   
    22.             foreach (var foodType in foodTypes) {  
    23.                 var results = this._food[foodType, foodType.ToString ()];  
    24.   
    25.                 Assert.IsNotNull (results);  
    26.   
    27.                 Assert.IsTrue (results.Length > 0);  
    28.   
    29.                 foreach (var food in results) {  
    30.                     Console.WriteLine ($"{foodType.ToString()} => {food} ");  
    31.                 }  
    32.             }  
    33.         }  
    34.   
    35.         [TestMethod]  
    36.         [ExpectedException (typeof (KeyNotFoundException))]  
    37.         public void Test_Get_FoodPyramid_Not_Exists () {  
    38.             var food = this._food[(FoodType) 3];  
    39.         }  
    40.   
    41.         [TestMethod]  
    42.         public void Test_Get_FoodPyramid_Not_Exists_Handle () {  
    43.             Exception expectedException = null;  
    44.   
    45.             try {  
    46.                 var food = this._food[(FoodType) 4];  
    47.             } catch (Exception ex) {  
    48.                 expectedException = ex;  
    49.             }  
    50.   
    51.             Assert.IsNotNull (expectedException);  
    52.         }  
    53.   
    54.     }  
    55. }  

Framework Common Library Which Uses Indexers

 
In this section, we will see the common libraries that are using the indexer feature.
 
Just a note. I tried to explore this so at least we are aware which libraries of the .NET Framework are using the indexer feature.
 
Please, see the code below, so we could extract a list of libraries that use this feature.
  1. using System;  
  2. using System.IO;  
  3. using System.Linq;  
  4. using Microsoft.VisualStudio.TestTools.UnitTesting;  
  5.   
  6. namespace UnitTestProject1 {  
  7.     [TestClass]  
  8.     public class LIbraryUsesIndexers {  
  9.         private const string FOLDER_NAME = "Microsoft.NET\\assembly\\GAC_64\\mscorlib\\v4.0_4.0.0.0__b77a5c561934e089\\mscorlib.dll";  
  10.   
  11.         [TestMethod]  
  12.         public void Test_MSCORLIB_Get_Libraries_Uses_Indexer_Feature () {  
  13.             string initPath = System.Environment.GetFolderPath (Environment.SpecialFolder.Windows);  
  14.   
  15.             string fullPath = Path.Combine (initPath, FOLDER_NAME);  
  16.   
  17.             var mscorLib = System.Reflection.Assembly.LoadFile (fullPath);  
  18.   
  19.             foreach (var itemType in mscorLib.DefinedTypes) {  
  20.                 var type = itemType.GetProperties ().Where (x => x.GetIndexParameters ().Length != 0).FirstOrDefault ();  
  21.   
  22.                 if (type != null) {  
  23.                     Console.WriteLine ($" Type: {itemType.FullName}, Property: {type}");  
  24.                 }  
  25.             }  
  26.         }  
  27.     }  
  28. }  
Please see the table for the list of libraries as the result of the above code sample.
 
Type Property
System.String Char Chars [Int32]
System.ParamsArray System.Object Item [Int32]
System.Security.AccessControl.GenericAcl System.Security.AccessControl.GenericAce Item [Int32]
System.Security.AccessControl.RawAcl System.Security.AccessControl.GenericAce Item [Int32]
System.Security.AccessControl.CommonAcl System.Security.AccessControl.GenericAce Item [Int32]
System.Security.AccessControl.SystemAcl System.Security.AccessControl.GenericAce Item [Int32]
System.Security.AccessControl.DiscretionaryAcl System.Security.AccessControl.GenericAce Item [Int32]
System.Security.AccessControl.AuthorizationRuleCollection System.Security.AccessControl.AuthorizationRule Item [Int32]
System.Security.Principal.IdentityReferenceCollection System.Security.Principal.IdentityReference Item [Int32]
System.Security.Policy.ApplicationTrustCollection System.Security.Policy.ApplicationTrust Item [Int32]
System.Diagnostics.Tracing.SessionMask Boolean Item [Int32]
System.Diagnostics.Tracing.EventPayload System.Object Item [System.String]
System.Collections.ArrayList System.Object Item [Int32]
System.Collections.BitArray Boolean Item [Int32]
System.Collections.ListDictionaryInternal System.Object Item [System.Object]
System.Collections.EmptyReadOnlyDictionaryInternal System.Object Item [System.Object]
System.Collections.Hashtable System.Object Item [System.Object]
System.Collections.IDictionary System.Object Item [System.Object]
System.Collections.IList System.Object Item [Int32]
System.Collections.SortedList System.Object Item [System.Object]
System.Collections.Concurrent.ConcurrentDictionary TValue Item [TKey]
System.Collections.ObjectModel.Collection T Item [Int32]
System.Collections.ObjectModel.ReadOnlyCollection T Item [Int32]
System.Collections.ObjectModel.ReadOnlyDictionary TValue Item [TKey]
System.Collections.ObjectModel.KeyedCollection TItem Item [TKey]
System.Collections.Generic.Dictionary TValue Item [TKey]
System.Collections.Generic.IDictionary TValue Item [TKey]
System.Collections.Generic.IList T Item [Int32]
System.Collections.Generic.IReadOnlyList T Item [Int32]
System.Collections.Generic.IReadOnlyDictionary TValue Item [TKey]
System.Collections.Generic.List T Item [Int32]
System.Reflection.ConstArray Byte Item [Int32]
System.Reflection.MetadataEnumResult Int32 Item [Int32]
 
Just a note about list of libraries. I did remove the backticks and the succeeding number to show clarity.
 
For example System.Collections.Generic.List`1 was converted into System.Collection.Generic.List.
 
Also, just a note, you might be wondering “why does it have a backtick and followed by a number?” To answer that.
  • Names of generic types ends with a backtick (`).
  • Then followed by digits. These digits are representing the number of generic type arguments.
  • The main purpose of this naming is to allow compilers to support generic types with the same name but with different numbers of type parameters.
Remarks
 
We can say that C# indexer is just syntactic sugar to help developers to encapsulate internal collections or arrays.
 
Hopefully, you have enjoyed this article.
 
Lastly, you can download the sample code here: https://github.com/jindeveloper/Csharp-Indexer-Feature.
 
Happy programming, until next time.