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.
- using System;
- using System.Linq;
-
- namespace CSharpIndexFeatureExplore {
- public enum Title {
- MR = 0,
- MS = 1
- }
-
- public class Customer {
- public Customer () : this (5) { }
-
- public Customer (int lengthOfCustomers) {
- this._customers = new Customer[lengthOfCustomers];
- }
- public Title Title { get; set; }
- public string TitleInString {
- get {
- string title = Title.ToString ();
-
- return new string (title.Select ((ch, index) => (index == 0) ? ch : Char.ToLower (ch)).ToArray ());
- }
- }
- public string FirstName { get; set; }
-
- public string LastName { get; set; }
-
- public int CustomersLength { get { return this._customers.Length; } }
-
- private readonly Customer[] _customers;
- public Customer this [int index] {
- get { return this._customers[index]; }
- set { this._customers[index] = value; }
- }
- public override string ToString () => $"{this.TitleInString}. {this.LastName}, {this.FirstName}";
- }
- }
Now let us try to make a unit-test so could see how indexers work.
- using System;
- using CSharpIndexFeatureExplore;
- using Microsoft.VisualStudio.TestTools.UnitTesting;
-
- namespace UnitTestProject1 {
-
- [TestClass]
- public class IndexFeatureInstanceList {
-
- private Customer _customer;
-
- [TestInitialize ()]
- public void TestInit () {
- this._customer = new Customer (5) { };
-
- this.Initialize_Customer_Collection ();
- }
-
- private void Initialize_Customer_Collection () {
- this._customer[0] = new Customer { Title = Title.MR, FirstName = "Red", LastName = "Ford" };
- this._customer[1] = new Customer { Title = Title.MS, FirstName = "Anne", LastName = "Green" };
- this._customer[2] = new Customer { Title = Title.MR, FirstName = "Jack", LastName = "Robinsons" };
- this._customer[3] = new Customer { Title = Title.MS, FirstName = "Michelle", LastName = "Miguelito" };
- this._customer[4] = new Customer { Title = Title.MS, FirstName = "Trix", LastName = "Delacruz" };
- }
-
- [TestMethod]
- [Priority (1)]
- public void Test_Create_Instances_Of_Customer_Class () {
-
- Assert.IsNotNull (_customer);
- Assert.IsTrue (_customer.CustomersLength > 0);
-
- }
-
- [TestMethod]
- [Priority (2)]
- public void Test_Loop_Throught_Customer_Instances () {
- int length = this._customer.CustomersLength;
-
- for (int i = 0; i < length; i++) {
- Console.WriteLine (this._customer[i].ToString ());
- }
- }
- }
- }
- If you need a collection of values directly related to your instance.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Runtime.CompilerServices;
-
- namespace CSharpIndexFeatureExplore {
- public enum FoodType {
- JUNK = 0,
- GO = 1,
- GROW = 2
- }
-
- public class Food {
- private Dictionary<FoodType, string[]> foodCollection;
-
- public Food () {
- foodCollection = new Dictionary<FoodType, string[]> { { FoodType.JUNK, new string[] { "Potato Chips", "Sweet Potato Chips" } },
- { FoodType.GO, new string[] { "Sweet Potatoes", "Rice", "Pasta", "Bread" } },
- { FoodType.GROW, new string[] { "Chicken", "Eggs", "Fish", "Meat" } }
- };
- }
-
- public int FruitsLength { get; set; }
-
- [IndexerName ("FoodPyramid")]
- public string[] this [FoodType index, string foodType = ""] {
- get {
- return foodCollection[index];
- }
-
- set {
- if (!Enum.GetNames (typeof (FoodType)).Any (f => f == foodType)) {
- throw new ApplicationException ("Food type not available");
- }
-
- foodCollection[index] = value;
- }
- }
- }
- }
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.
- Imports CSharpIndexFeatureExplore
-
- Module Module1
-
- Sub Main()
- Dim fruits As New Food()
- Dim myCollectionOfFruits = fruits.FoodPyramid(1)
-
- For Each item As String In myCollectionOfFruits
- Console.WriteLine(item)
- Next
-
- End Sub
-
- End Module
Again, let us try to make a unit-test so could see how indexers work in this scenario.
- using System;
- using System.Collections.Generic;
- using CSharpIndexFeatureExplore;
- using Microsoft.VisualStudio.TestTools.UnitTesting;
-
- namespace UnitTestProject1 {
-
- [TestClass]
- public class IndexFeaturesRepresentsList {
-
- private Food _food;
-
- [TestInitialize ()]
- public void Init () {
- this._food = new Food ();
- }
-
- [TestMethod]
- public void Test_Get_FoodPyramid () {
- var foodTypes = (FoodType[]) Enum.GetValues (typeof (FoodType));
-
- foreach (var foodType in foodTypes) {
- var results = this._food[foodType, foodType.ToString ()];
-
- Assert.IsNotNull (results);
-
- Assert.IsTrue (results.Length > 0);
-
- foreach (var food in results) {
- Console.WriteLine ($"{foodType.ToString()} => {food} ");
- }
- }
- }
-
- [TestMethod]
- [ExpectedException (typeof (KeyNotFoundException))]
- public void Test_Get_FoodPyramid_Not_Exists () {
- var food = this._food[(FoodType) 3];
- }
-
- [TestMethod]
- public void Test_Get_FoodPyramid_Not_Exists_Handle () {
- Exception expectedException = null;
-
- try {
- var food = this._food[(FoodType) 4];
- } catch (Exception ex) {
- expectedException = ex;
- }
-
- Assert.IsNotNull (expectedException);
- }
-
- }
- }
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.
- using System;
- using System.IO;
- using System.Linq;
- using Microsoft.VisualStudio.TestTools.UnitTesting;
-
- namespace UnitTestProject1 {
- [TestClass]
- public class LIbraryUsesIndexers {
- private const string FOLDER_NAME = "Microsoft.NET\\assembly\\GAC_64\\mscorlib\\v4.0_4.0.0.0__b77a5c561934e089\\mscorlib.dll";
-
- [TestMethod]
- public void Test_MSCORLIB_Get_Libraries_Uses_Indexer_Feature () {
- string initPath = System.Environment.GetFolderPath (Environment.SpecialFolder.Windows);
-
- string fullPath = Path.Combine (initPath, FOLDER_NAME);
-
- var mscorLib = System.Reflection.Assembly.LoadFile (fullPath);
-
- foreach (var itemType in mscorLib.DefinedTypes) {
- var type = itemType.GetProperties ().Where (x => x.GetIndexParameters ().Length != 0).FirstOrDefault ();
-
- if (type != null) {
- Console.WriteLine ($" Type: {itemType.FullName}, Property: {type}");
- }
- }
- }
- }
- }
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.
Happy programming, until next time.