Diving Into OOP (Day 8) - Indexers in C# (A Practical Approach)

Table of Contents

  • Introduction
  • Indexers in C# (The definition)
  • Roadmap
  • Indexers (The explanation)
    • Lab 1
    • Lab 2
    • Lab 3
  • Data-Types in Indexers
    • Lab 1
  • Indexers in interfaces
  • Indexers in Abstract class
  • Indexer Overloading
    • Point to remember
  • Static Indexers?
    • Point to remember
  • Inheritance/Polymorphism in Indexers
  • .NET Framework and Indexers
    • Point to remember
  • Properties vs Indexers
  • Conclusion

Introduction

In my last article of this series we learned about properties in C#. This article of the series “Diving into OOP” will explain all about indexers in C#, their uses and practical implementation. We'll follow the same way of learning, in other words less theory and more practice. I'll try to explain the concept in-depth.

Indexers in C# (The definition)

Let's use the definition from: MSDN.

Indexers allow instances of a class or struct to be indexed just like arrays. Indexers resemble properties except that their accessors take parameters.”

Roadmap

Let's recall our road map.

Roadmap

Indexers (The explanation)

Indexers

As the definition says, indexers allow us to leverage the capability of accessing the class objects as an array. For a better understanding, create a console application named Indexers and add a class to it named Indexer. We'll use this class and project to learn Indexers. Make the class public, do not add any code for now and in Program.cs add following code.

Lab 1

  1. namespace Indexers  
  2. {  
  3.     class Program  
  4.     {  
  5.         static void Main(string[] args)  
  6.         {  
  7.             Indexer indexer=new Indexer();  
  8.             indexer[1] = 50;  
  9.         }  
  10.     }  

Compile the code. We get:

Error Cannot apply indexing with [] to an expression of type 'Indexers.Indexer'

I just created an object of the Indexer class and tried to use that object as an array. Since it was not actually an array, it produced a compile time error.

Lab 2

Indexer.cs

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5.   
  6. namespace Indexers  
  7. {  
  8.     public class Indexer  
  9.     {  
  10.         public int this[int indexValue]  
  11.         {  
  12.             set  
  13.             {  
  14.                 Console.WriteLine("I am in set : Value is " + value + " and indexValue is " + indexValue);  
  15.                 Console.ReadLine();  
  16.             }  
  17.         }  
  18.     }  

Program.cs

  1. namespace Indexers  
  2. {  
  3.     class Program  
  4.     {  
  5.         static void Main(string[] args)  
  6.         {  
  7.             Indexer indexer=new Indexer();  
  8.             indexer[1] = 50;  
  9.         }  
  10.     }  

Output

console

Here we just used an indexer to index my object of the class Indexer. Now my object can be used as an array to access various object values.

Implementation of indexers is derived from a property known as “this”. It takes an integer parameter indexValue. Indexers are different from properties. In properties, when we want to initialize or assign a value, the “set” accessor, if defined, is automatically called. And the keyword “value” in the “set” accessor was used to hold or keep track of the assigned value to our property. In the preceding example, indexer[1] = 50; calls the “set” accessor of the “this” property, in other words the indexer 50 becomes the value and 1 becomes the index of that value.

Lab 3

Indexer.cs

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5.   
  6. namespace Indexers  
  7. {  
  8.     public class Indexer  
  9.     {  
  10.         public int this[int indexValue]  
  11.         {  
  12.             set  
  13.             {  
  14.                 Console.WriteLine("I am in set : Value is " + value + " and indexValue is " + indexValue);  
  15.             }  
  16.              get  
  17.             {  
  18.                 Console.WriteLine("I am in get and indexValue is " + indexValue);  
  19.                 return 30;  
  20.             }  
  21.         }  
  22.     }  

Program.cs

  1. using System;  
  2.   
  3. namespace Indexers  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.             Indexer indexer=new Indexer();  
  10.             Console.WriteLine(indexer[1]);  
  11.             Console.ReadKey();  
  12.         }  
  13.     }  

Output

see output

In the preceding code snippet, I used get as well, to access the value of the indexer. Properties and Indexers work on the same set of rules. There is a bit of a difference on how we use them. When we do indexer[1] then that means the “get” accessor is called and when we assign some value to indexer[1] then the “set” accessor is called. When implementing indexer code we need to take care that when we access an indexer it is accessed in the form of a variable and that too is an array parameter.

Data-Types in Indexers

Lab 1

Indexer.cs

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5.   
  6. namespace Indexers  
  7. {  
  8.     public class Indexer  
  9.     {  
  10.         public int Index;  
  11.         public int this[string indexValue]  
  12.         {  
  13.             set  
  14.             {  
  15.                 Console.WriteLine("I am in set : Value is " + value + " and indexValue is " + indexValue);  
  16.                 Index = value;  
  17.             }  
  18.              get  
  19.             {  
  20.                 Console.WriteLine("I am in get and indexValue is " + indexValue);  
  21.                 return Index;  
  22.             }  
  23.         }  
  24.     }  

Program.cs

  1. using System;  
  2.   
  3. namespace Indexers  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.             Indexer indexer=new Indexer();  
  10.             indexer["name"]=20;  
  11.             Console.WriteLine(indexer["name"]);  
  12.             Console.ReadKey();  
  13.         }  
  14.     }  

Output

show output

The “this” property, in other words indexers, have a return value. In our example the return value was an integer. The square brackets along with “this” can also hold other data types and not only an integer. In the preceding example I tried to explain this using a string parameter type for “this” : public int this [string indexValue].

The string parameter “indexValue” has a value “name”, like we ed in the Main method of Program.cs. So one can have more than one indexer in a class deciding what should be the data type of the parameter value of an array. An indexer, like properties, follow the same rules of inheritance and polymorphism.

Indexers in interfaces

Like Properties and Methods, Indexers can also be declared in interfaces.

For practical implementation, just create an interface named IIndexers with the following code.

  1. namespace Indexers  
  2. {  
  3.     interface IIndexers  
  4.     {  
  5.         string this[int indexerValue] { getset; }  
  6.     }  

Here, an indexer is declared with an empty get and set accessor, that returns string values.

Now we need a class that implements this interface. You can define a class of your choice and implement that using the IIndexers interface.

Indexer.cs

  1. using System;  
  2.   
  3. namespace Indexers  
  4. {  
  5.     public class IndexerClass:IIndexers  
  6.     {  
  7.         readonly string[] _nameList = { "AKhil","Bob","Shawn","Sandra" };  
  8.   
  9.         public string this[int indexerValue]  
  10.         {  
  11.             get  
  12.             {  
  13.                 return _nameList[indexerValue];  
  14.             }  
  15.             set  
  16.             {  
  17.                 _nameList[indexerValue] = value;  
  18.             }  
  19.         }  
  20.     }  

The class has a default array of strings that holds names. Now we can implement an interface defined indexer in this class to write our custom logic to fetch names on the base of indexerValue. Let's call this in our main method.

Program.cs

  1. using System;  
  2.   
  3. namespace Indexers  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.            IIndexers iIndexer=new IndexerClass();  
  10.             Console.WriteLine(iIndexer[0]);  
  11.             Console.WriteLine(iIndexer[1]);  
  12.             Console.WriteLine(iIndexer[2]);  
  13.             Console.WriteLine(iIndexer[3]);  
  14.             Console.ReadLine();  
  15.   
  16.         }  
  17.     }  

Run the application.

Output

Run the application

In the main method, we took an interface reference to create an object of the Indexer class and we accessed that object array using indexer values like an array. It gives the names one by one.

Now if I want to access a “set” accessor as well, I can easily do that. To check this, just add two more lines where you set the value in the indexer.

  1. iIndexer[2] = "t;Akhil Mittal";  
  2. Console.WriteLine(iIndexer[2]); 

I set the value of the second element as a new name. Let's see the output.

see the output

Indexers in Abstract class

Like we used indexers in interfaces, we can also use indexers in abstract classes. I'll use the same logic of source code that we used in interfaces, so that you can relate how it works in abstract class as well. Just define a new class that should be abstract and should contain an abstract indexer with empty get and set.

AbstractBaseClass

  1. namespace Indexers  
  2. {  
  3.     public abstract class AbstractBaseClass  
  4.     {  
  5.         public abstract string this[int indexerValue] { getset; }  
  6.     }  

Define the derived class, inheriting from the abstract class.

IndexerClass

We here use an override in the indexer to override the abstract indexer declared in the abstract class.

  1. using System;  
  2.   
  3. namespace Indexers  
  4. {  
  5.     public class IndexerClass:AbstractBaseClass  
  6.     {  
  7.         readonly string[] _nameList = { "AKhil","Bob","Shawn","Sandra" };  
  8.   
  9.         public override string this[int indexerValue]  
  10.         {  
  11.             get  
  12.             {  
  13.                 return _nameList[indexerValue];  
  14.             }  
  15.             set  
  16.             {  
  17.                 _nameList[indexerValue] = value;  
  18.             }  
  19.         }  
  20.     }  

Program.cs

We'll use a reference of an abstract class to create an object of the Indexer class.

  1. using System;  
  2.   
  3. namespace Indexers  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.            AbstractBaseClass absIndexer=new IndexerClass();  
  10.             Console.WriteLine(absIndexer[0]);  
  11.             Console.WriteLine(absIndexer[1]);  
  12.             Console.WriteLine(absIndexer[2]);  
  13.             Console.WriteLine(absIndexer[3]);  
  14.             absIndexer[2] = "Akhil Mittal";  
  15.             Console.WriteLine(absIndexer[2]);  
  16.   
  17.             Console.ReadLine();  
  18.   
  19.         }  
  20.     }  

Output

run program

All of the preceding code is self-explanatory. You can explore more scenarios by yourself for a better understanding.

Indexer Overloading

Indexer.cs

  1. using System;  
  2.   
  3. namespace Indexers  
  4. {  
  5.     public class Indexer  
  6.     {  
  7.         public int this[int indexerValue]  
  8.         {  
  9.             set  
  10.             {  
  11.                 Console.WriteLine("Integer value " + indexerValue + " " + value);  
  12.             }  
  13.         }  
  14.   
  15.         public int this[string indexerValue]  
  16.         {  
  17.             set  
  18.             {  
  19.                 Console.WriteLine("String value " + indexerValue + " " + value);  
  20.             }  
  21.         }  
  22.   
  23.         public int this[string indexerValue, int indexerintValue]  
  24.         {  
  25.             set  
  26.             {  
  27.                 Console.WriteLine("String and integer value " + indexerValue + " " + indexerintValue + " " + value);  
  28.             }  
  29.         }  
  30.     }  

Program.cs

  1. using System;  
  2.   
  3. namespace Indexers  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.             Indexer indexer=new Indexer();  
  10.             indexer[1] = 30;  
  11.             indexer["name"]=20;  
  12.             indexer["address",2] = 40;  
  13.             Console.ReadLine();  
  14.         }  
  15.     }  

Output

run

In the preceding example, we see the indexer's signature is in the actual count of the actual parameters and data types irresepective of the names of the arguments/parameters or return value of the indexers. This allows us to overload indexers as we do in method overloading. You can read more about method over loading. Here now we have overloaded indexers that take integer, string integer and string combined as actual parameters. Like methods cannot be overloaded on the base of return types, so are indexers. Indexers follow the same methodology of overload like methods do.

Point to remember

Like indexers, we cannot overload properties. Properties are more like knowing by name and indexers on the other hand is more like knowing by signature.

Static Indexers

In the example that we discussed in the last section, just add a static keyword to the indexer signature.

  1. public static int this[int indexerValue]  
  2. {  
  3.     set  
  4.     {  
  5.         Console.WriteLine("Integer value " + indexerValue + " " + value);  
  6.     }  

Compile the program. We get a compile time error.

Error The modifier 'static' is not valid for this item

The error clearly indicates that an indexer cannot be marked static. An indexer can only be a class instance member but not static, on the other hand a property can be static too.

Point to remember

Properties can be static but indexers cannot be.

Inheritance/Polymorphism in Indexers

Indexer.cs

  1. using System;  
  2.   
  3. namespace Indexers  
  4. {  
  5.     public class IndexerBaseClass  
  6.     {  
  7.         public virtual int this[int indexerValue]  
  8.         {  
  9.             get  
  10.             {  
  11.                 Console.WriteLine("Get of IndexerBaseClass; indexer value: " + indexerValue);  
  12.                 return 100;  
  13.             }  
  14.             set  
  15.             {  
  16.                 Console.WriteLine("Set of IndexerBaseClass; indexer value: " + indexerValue + " set value " + value);  
  17.             }  
  18.   
  19.         }  
  20.     }  
  21.     public class IndexerDerivedClass:IndexerBaseClass  
  22.     {  
  23.         public override int this[int indexerValue]  
  24.         {  
  25.             get  
  26.             {  
  27.                 int dValue = base[indexerValue];  
  28.                 Console.WriteLine("Get of IndexerDerivedClass; indexer value: " + indexerValue + " dValue from base class indexer: " + dValue);  
  29.                 return 500;  
  30.             }  
  31.             set  
  32.             {  
  33.                 Console.WriteLine("Set of IndexerDerivedClass; indexer value: " + indexerValue + " set value " + value);  
  34.                 base[indexerValue] = value;  
  35.             }  
  36.   
  37.         }  
  38.     }  

Program.cs

  1. using System;  
  2.   
  3. namespace Indexers  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.             IndexerDerivedClass indexDerived=new IndexerDerivedClass();  
  10.             indexDerived[2] = 300;  
  11.             Console.WriteLine(indexDerived[2]);  
  12.             Console.ReadLine();  
  13.   
  14.         }  
  15.     }  
  16. }

Output

Output

The example code taken above explains run time polymorphism and inheritance in indexers. I created a base class named IndexerBaseClass having an indexer with its own get and set as was explained in prior examples. Thereafter a derived class is created named IndexerDerivedClass, this derives from IndexerBaseClass and overrides the “this” indexer from the base class. Note that the base class indexer is marked virtual, so we can override it in a derived class by marking it “override” in the derived class. The example makes a call to the indexer of the base class. Sometimes when we need to override code in a derived class in the derived class, we may require the base class indexer to be called first. This is just a situation. The same rule of run time polymorphism applies here, we declare the base class indexer and virtual and derived class one as override. In the “set” accessor of the derived class, we can call the base class indexer as base[indexerValue]. Also this value is used to initialize the derived class indexer as well. So the value is stored in the “value” keyword too. So, indexDerived[2] in the Main() method of Program.cs is replaced to base[2] in the “set” accessor. Whereas in the “get” accessor it is the reverse, we must put base[indexerValue] on the right hand side of the equal sign. The “get” accessor in the base class returns the value 100 that we get in the dValue variable.

.NET Framework and Indexers

Indexers play a crucial role in the .NET Framework. Indexers are widely used in the .NET Framework builtin classes, libraries such as collections and enumerable. Indexers are used in collections that are searchable like Dictionary, Hashtable, List, Arraylist and so on.

Point to remember

Dictionary in C# largely uses indexers to have a staring parameter as an indexer argument.

Classes like ArrayList and List use indexers internally to provide functionality of arrays for fetching and using the elements.

Properties vs Indexers

I have already explained a lot about properties and indexers, to summarize, let me point to an MSDN link for a better explanation.

table

Table taken from MSDN.

Conclusion

With this article we completed nearly all the scenarios related to an indexer. We did many hands-on labs to clarify our concepts. I hope my readers now know by heart about these basic concepts and will never forget them. These may also help you in cracking C# interviews.

smile

Keep coding and enjoy reading.

Also do not forget to rate/comment/like my article if it helped you by any means, this motivates and encourages me to write more and more.

Read more:

For more technical articles you can reach out to CodeTeddy

My other series of articles:

Happy coding !