Behind the Scenes Look at C#: Indexers

Indexers help you to organize your data and get to it more quickly. In order to use one, however, there are certain steps we must take. Michael Youssef details those steps in this article.

Contributed by
Rating: 5 stars5 stars5 stars5 stars5 stars / 28
August 10, 2005
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

An index is an abstract concept. In order to gain faster access to some data that you have (in a form of object or any other construction), you index the data. In C# an index is a construction (actually a property as we will see soon) that supports the access to an internal collection (maybe an array or a collection class) of data about a given object. For example, any Department contains more than one Employee. When we want to write the Department and the Employee class, we need a way to describe that the Department class can have many Employee instances (Employees) and the client code can access these internal instances in a common way. In this situation we need an indexer to do the job.

There are steps that we need to take. First we need to define the internal storage mechanism for the Employee instances in the Department class (we can do that by using a simple array or a complex collection class). Note that without the internal array or collection the indexer doesn't make any sense; its role to give the client code access to the internal array. Second, we need to write the indexer to provide the client code with an access mechanism. The indexer looks like this:

public Employee this[int arg]
{
  get
  {
    return this.Employees[arg];
  }

  set
  {
    this.Employees[arg] = value;
  }
}

As you can see, the indexer defines both the get and set accessors as much as a property does, and it uses the implicit argument value in the same way that a set accessor uses it. In this example, we define the indexer using the "this" keyword. The indexer's parameter is the index that you will use to get and set the value into the internal array, as we will see shortly.

The indexer uses the keyword this to refer to the current instance of the Department object. This makes sense because it's as if we are saying "put this Employee instance in me." "Me" in this case refers to the Department object, and the indexer is the default property of the object, so "me" means "in my indexer." A class can have only one indexer, but we can overload this indexer to accept varies parameters.

In our example, the class Department is considered to be a container class because it contains a collection of other objects and, as you will see, the relationship between the Department class and the Employee class is one-to-many. There are many Employee instances in the Department instance, and the Department instance has more than one Employee instance. This is similar to one-to-many relationships in relational database systems.

Using indexers, we will use the index operator ([]) with our user-defined classes (the Department class in this example) in the same way as we use the index operator with the array to access its individual members. We are going to see that in the example.

Let's take a look at our first indexer example.

C# Indexers Example (First Iteration)

Please copy the following code and compile it:

using System;
using System.Text;

namespace indexers
{
  class Class1
  {
    static void Main(string[] args)
    {
      Department dept = new Department();
      dept.Name = "IT";

      // creating the first Employee object
      Employee emp1 = new Employee();  
      emp1.FirstName = "Maria"; 
      emp1.LastName = "Jack";

      // creating the other employee objects
      Employee emp2 = new Employee();
      emp2.FirstName = "Mina";
      emp2.LastName = "Jack";

      Employee emp3 = new Employee();
      emp3.FirstName = "Mick";
      emp3.LastName = "Nelson";

      Employee emp4 = new Employee();
      emp4.FirstName = "John";
      emp4.LastName = "smith"; 

      Employee emp5 = new Employee();  
      emp5.FirstName = "Joly"; 
      emp5.LastName = "Mac";

      Employee emp6 = new Employee();  
      emp6.FirstName = "Steven";
      emp6.LastName = "Joe";

      // using the indexer
      dept[0] = emp1;
      dept[1] = emp2;
      dept[2] = emp3;
      dept[3] = emp4;
      dept[4] = emp5;
      dept[5] = emp6;

      Console.WriteLine(dept.ToString());
      Console.ReadLine(); 
    }
  }

  class Department
  {
    private string name;
    private Employee[] Employees = new Employee[6]; 
 
    public Employee this[int arg]
    { 
      get
      {
        return this.Employees[arg];
      }

      set
      {
        this.Employees[arg] = value;
      }
    }

    public string Name
    {
      get
      {
        return this.name;
      }

      set
      {
        this.name = value;
      }
    }

    public override string ToString()
    {
      StringBuilder temp = new StringBuilder();
      temp.Append("The department "+this.name+" contains"+
" the following Employees\n");

      foreach(Employee emp in Employees)
      {
        temp.Append("\n" + emp.ToString()); 
      } 
      return temp.ToString(); 
    } 

  }

  class Employee
  {
    private string firstName;
    private string lastName;

    public string FirstName
    {   
      get
      {
        return this.firstName;
      }

      set
      {
        this.firstName = value;
      }
    }

    public string LastName
    {
      get
      {
        return this.lastName;
      }

      set
      { 
        this.lastName = value;
      } 
    }

    public override string ToString()
    {
      return this.firstName+" "+this.lastName;
    }

  }
}

Run the application to get the following result to the console window:

Explanation of code

We got the expected output to the console window. The Department object contains all these instances of the Employee class, which it stores internally using the private array. Let's go through the code. We will begin with the Employee class.

class Employee
{
  private string firstName;
  private string lastName;

  public string FirstName
  {
    get
    {
      return this.firstName;
    }

    set
    {
      this.firstName = value;
    }
  }

  public string LastName
  {
    get
    { 
      return this.lastName;
    }

    set
    {
      this.lastName = value;
    }
  }

  public override string ToString()
  {
    return this.firstName+" "+this.lastName;
  } 

}

There's nothing new about the Employee class. It defines two private fields (the firstName and the lastName fields) of data type string. It also defines two public properties (FirstName and LastName) to access the private fields, then it overrides the base class's method ToString() to return a representation of the Employee instance. Let's look at the Department class:

class Department
{
  private string name;
  private Employee[] Employees = new Employee[6];

  public Employee this[int arg]
  {
    get
    {
      return this.Employees[arg];
    }

    set
    {
      this.Employees[arg] = value;
    }
  }

  public string Name
  { 
    get
    {
      return this.name;
    }

    set
    {
      this.name = value;
    }
  }

  public override string ToString()
  { 
    StringBuilder temp = new StringBuilder();
    temp.Append("The department "+this.name+" contains"+
" the following Employees\n");

    foreach(Employee emp in Employees)
    {
      temp.Append("\n" + emp.ToString());
    }

    return temp.ToString(); 
  }

}

The Department class defines a private array of type Employee that contains six elements which are used by the indexer to manipulate the objects. The Department class defines the indexer that looks much like a property, but the indexer as we have said uses the keyword "this" to refer to the current Department object. Note that the return type is an Employee instance, so in the get accessor we can return an instance of that type.

With the indexer we don't use parentheses; we use the index operator in both the definition of the index, and when we use it. Actually we are overriding the index operator to work with our class in much the same way as it works with arrays. If you look at the get and set accessor you will not find anything new. The get accessor simply returns the object stored at the specific index of the internal array (the indexer passes the index value as an int to the internal array). The set accessor sets the object reference (using the keyword value) and sets it as the specific index in the internal array object. There is no magic involved here.

The ToString() method of the Department class uses the class StringBuilder (that's why we have used the namespace System.Text, because this class lives in this namespace) which provides us with a dynamic string builder class. Normally if we have used the string data type it will create a new string object each time we modify the string, and refer to that new object. This can harm the performance of the application, but the class StringBuilder supports string modifications without creating a new object.

The ToString method creates the object temp of type StringBuilder, then it uses the StringBuilder's method "append" to append a string to the object. It uses a foreach loop to iterate through all the Employee instances in the internal Employees array object. It then appends to the StringBuilder object the string that returns from a call to each Employee's ToString method. After an iteration though all the Employee instances it returns the temp.ToString() method as the string of the Department.ToString() method.

In the Main method we simply creates six instances of type Employee, then we assign the six references to the internal array of the dept object using the indexer. We end the Main method with a call to the dept.ToString() method, which iterates through the internal array elements and returns a string representing the dept object. In this case we return all the first and last names of the employees and the name of the department. Let's take a look at the MSIL generated code for this application.

A Look at the MSIL Code

Load the application with the Ildasm.exe tool and expand the Department and the Class1 classes. You will get the following figure:

As we have said, the indexer is implemented down to the MSIL code as a default property called "item," and it creates get_Item method and set_Item method in much the same way that any property does. Double click on the Item property and you will get the following MSIL code:

.property instance class indexers.Employee
Item(int32)
{
  .get instance class indexers.Employee indexers.Department::get_Item(int32)
  .set instance void indexers.Department::set_Item(int32,
class indexers.Employee)
} // end of property Department::Item

 

As you can see, it's a property declaration that contains the get and set method declarations too. Double click on the get_Item method to get to the following MSIL code:

.method public hidebysig specialname instance class indexers.Employee
get_Item(int32 arg) cil managed
{
  // Code size 13 (0xd)
  .maxstack 2
  .locals init ([0] class indexers.Employee CS$00000003$00000000)
  IL_0000: ldarg.0
  IL_0001: ldfld class indexers.Employee[]  indexers.Department::Employees
  IL_0006: ldarg.1
  IL_0007: ldelem.ref
  IL_0008: stloc.0
  IL_0009: br.s IL_000b
  IL_000b: ldloc.0
  IL_000c: ret
} // end of method Department::get_Item

This is much the same as the code of any other get accessor method. The one thing that you should note is that the method contains a new instruction, which is the ldelem.ref instruction. This stands for "load element reference," and it does what its name says. This is an element loading instruction; it takes the element index and an object reference of the Stack and then puts the value of the element on the stack. The ref part of the instruction means that it will load an object reference. Note that the get method takes a parameter of type int32 as you can see in the MSIL code. Double click on the method set_Item to get to the following code:

.method public hidebysig specialname instance void
set_Item(int32 arg,
class indexers.Employee 'value') cil managed
{
  // Code size 10 (0xa)
  .maxstack 3
  IL_0000: ldarg.0
  IL_0001: ldfld class indexers.Employee[]  indexers.Department::Employees
  IL_0006: ldarg.1
  IL_0007: ldarg.2
  IL_0008: stelem.ref
  IL_0009: ret
} // end of method Department::set_Item


The only new instruction is the stelem.ref instruction, which stands for "Store Element Reference." This is called an element storing instruction. It takes the value and the object reference and stores them, and it doesn't put anything on the Stack. Note that the set_Item accessor method takes two parameters (not one as in our ordinary properties); the first is the index and the second is the parameter value (of type Employee). It's something like stating where you want store this object reference. Load the Main method, and I'm sure that the code will not come as a surprise for you. I will copy just a snippet of the code here.

IL_00da: ldloc.0
IL_00db: ldc.i4.3
IL_00dc: ldloc.s emp4
IL_00de: callvirt instance void indexers.Department::set_Item(int32,
class indexers.Employee)


IL_00e3: ldloc.0
IL_00e4: ldc.i4.4
IL_00e5: ldloc.s emp5
IL_00e7: callvirt instance void indexers.Department::set_Item(int32,
class indexers.Employee)


IL_00ec: ldloc.0
IL_00ed: ldc.i4.5
IL_00ee: ldloc.s emp6
IL_00f0: callvirt instance void indexers.Department::set_Item(int32,
class indexers.Employee)

All I want to mention about this code is the set_Item method of each object. Simply, this code loads the object onto the Stack and then assigns it to the internal array object using the indexer property's method set_Item and, as we just said, this method takes two parameters (the value of the array index and the object reference it will stores). In the second part of this tutorial we will extend the example to include the Department's class, add another indexer to this class, and then replace all the indexers with an advanced array object.   

blog comments powered by Disqus
C# ARTICLES

- Beginning C#
- ASP.NET RedirectPermanent Method using C# an...
- C Programming Language and UNIX Pioneer Pass...
- Using Facebook JavaScript SDK in ASP.NET wit...
- ASP.NET Export to Excel and Word using VB.NE...
- WAV and MP3 Streaming with ASP.Net and C#
- Game Programming using SDL: the File I/O API
- C# and Java Developer Jobs on the Rise
- The Future Evolution of C# and VB.NET
- C# If and Else-if Statements
- How To Use the C# String Replace Method
- 5 Ways to Parse XML in C#
- C# Meets Design Patterns
- Coding a CRC-Generating Algorithm in C
- Cyclic Redundancy Check

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 3 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials