Learning C# and OOP, Indexers, Part 1

Richard Baldwin shows you how to use indexers in C# along with indexed properties in Java, and provides a comparison between the two.

Published:  October 31, 2002
By Richard G. Baldwin

C# Programming Notes # 108a


Preface

This lesson is part of a miniseries designed to teach you how to write object-oriented programs using C#.  The first lesson in the miniseries was entitled Learning C# and OOP: Getting Started, Objects and Encapsulation.  The previous lesson was entitled Learning C# and OOP, Properties, Part 2.

Comparisons with Java

The miniseries will emphasize the similarities between C# and Java with respect to both syntax and OOP concepts.  By studying these lessons and learning about OOP using C#, you will also be learning quite a lot about OOP using Java.

No prerequisite C# or OOP knowledge required

The miniseries assumes no prerequisite knowledge of C# syntax or OOP concepts.  If you have a general understanding of computer programming, you should be able to read and understand the lessons in this miniseries.

Viewing tip

You may find it useful to open another copy of this lesson in a separate browser window.  That will make it easier for you to scroll back and forth among the different listings while you are reading about them.

Supplementary material

I recommend that you also study the other lessons in my extensive collection of online programming tutorials.  You will find a consolidated index at www.DickBaldwin.com.

Preview

Simple properties

In the previous lesson, you learned about the use of simple properties in both C# and Java.  You learned that the most important aspect of properties in both programming environments is their use in reflection in C# and introspection in Java.  I showed you an example of those processes in both languages.

Indexed properties

What I didn't tell you in the previous lesson is that both C# and Java support the concept of indexed properties.  An indexed property is a property that is capable of storing more than one value based on an index, and is capable of returning any of those values based on an index.

(Properties of this type are most commonly referred to as indexed properties in Java, while they are more commonly referred to simply as indexers in C#.  However, I will discuss a sample program in this lesson that will demonstrate that they are also identified as properties by the reflection process in C#.)

Programming C# by Jesse Liberty

Here is what Jesse Liberty has to say about properties in his O'Reilly book entitled Programming C#:

"An indexer is a C# construct that allows you to access collections contained by a class using the familiar [] syntax of arrays.  An indexer is a special kind of property and includes get() and set() methods to specify its behavior. ... Although it is common to use integers as index values, you can index a collection on other types as well, including strings.  You can even provide an indexer with multiple parameters to create a multidimensional array!"

C# In A Nutshell

Here is what the authors of C# In A Nutshell from O'Reilly have to say on the matter:

"Indexers provide a natural way of indexing elements in a class or struct that encapsulate a collection, via an array's [] syntax.  Indexers are similar to properties, but are accessed via an index, as opposed to a property name.  The index can be any number of parameters."

C# Primer, A Practical Approach

Here is part of what Stanley Lippman has to say in C# Primer, A Practical Approach from Addison-Wesley:

"An indexer provides support for arraylike indexing of a class object.  An indexer looks like a property.  Like a property, an indexer provides a get and set pair of accessors.  Unlike a property, however, an indexer is identified using the this keyword rather than a name.  Indexers require at least one index parameter.  The index can be of any type."

What does Dick Baldwin have to say?

I will add to the above by pointing out some of the differences between indexers in C# and indexed properties in Java.

Liberty makes a good distinction between the return type of a C# indexer and the type argument of the indexer.  He explains:

"The return type determines the type of object that will be returned by the indexer, while the type argument specifies what kind of argument will be used to index into the collection that contains the target objects."

One indexer per type argument per object

C# allows only one indexer of a given type argument per object.  For example, an object can provide only one indexer with a type argument of type int

However, C# indexers can be overloaded such that a given object can have more than one indexer so long as the type arguments for the different indexers are different.  On the other hand, the different overloaded indexers supported by a single C# object do not have unique names.  They are essentially distinguished from one another by their type arguments.

Will illustrate overloaded indexers

Overloaded indexers will be illustrated in the sample program that that I will discuss later.  In that program, a single C# object provides two different indexers.  One of the indexers has a type argument of type int and a return type of double.  The other indexer has a type argument of type string, and a return type of char.

Any number of indexed properties

Java, on the other hand, allows any number of indexed properties to be contained in a single object.  Every indexed property has a unique name and must be indexed by an integer.  Each indexed property can have any return type, including the primitive types, class types, and interface types.

Discussion and Sample Code

Two separate programs

I will present and discuss two separate programs in this lesson.  The first program, named Indexer01, is a C# program that illustrates the use of overloaded indexers.  Although I will discuss this program in fragments, you can view the entire program in Listing C6 near the end of the lesson.

The second program that I will discuss is a Java program named IndexProp01.  This program will use indexed properties to emulate the behavior of the C# program mentioned above.  (At least, it will emulate that behavior to the extent that indexed properties in Java can be used to emulate C# indexers.)  You can view the entire Java program in Listing J6 near the end of the lesson.

The C# program named Indexer01

The first code fragment for the C# program is shown in Listing C1.  This program illustrates overloaded indexers.  Two indexers are defined in the same class.  One of the indexers has a type argument of type int and a return type of double.  The other indexer has a type argument of type string, and a return type of char.

Listing C1 shows the beginning of the class named IdxCls.  This code fragment includes the complete definition of the indexer with the type argument of type int and the return type of type char.  I have highlighted certain important aspects of the indexer syntax in boldface.

class IdxCls{
  //Indexer for index type int
  double[] array =new double[5];
  public double this[int i]{
    set{
      //validating code if desired  
      array[i] = value;
    }//end set
    
    get{
      //additional code if desired
      return array[i];
    }//end get
  }//end indexer

Listing C1

Similar to a simple property

You will note that certain aspects of the indexer syntax are very similar to the property syntax discussed in the previous lesson.  (However, there are also some significant differences.) 

For example, both a property and an indexer have a set block and a get block.  (The set block could be absent for a read-only property or a read-only indexer.)  The code within those blocks will usually be different between a property and an indexer due simply to the fact that a property deals with only one value while an indexer deals with multiple values.

The return type

Starting at the top of Listing C1, both an indexer and a property specify a return type.  The indexer in Listing C1 specifies a return type of double (the type specification immediately to the left of the keyword this). 

Indexer doesn't have a unique name

If you refer back to the previous lesson, you will see that a property specifies a unique property name immediately following the return type.  However, an indexer does not specify a name.  Rather, an indexer specifies this, which is not a name.  Rather, the keyword this is a reference to this object.  The lack of a unique name for an indexer explains why an object can have only one indexer with a given type argument.

Method and indexer overloading

In the same sense that C# and Java allow overloaded methods (two or more methods in the same scope with the same name but different argument types), C# allows two or more indexers in the same object so long as they have different type arguments.

The type argument and index parameter

Immediately following the keyword this, an indexer specifies a type argument and a parameter name in a pseudo-array notation enclosed in square brackets.  (See [int i] in Listing C1.)  This is the index parameter that is used by the code in the set and get blocks to refer to a specific value stored in a collection.

The hidden parameter named value

As is the case with a property, the value to be stored (the right operand of an assignment operator, for example) is passed to the set block of an indexer as a hidden parameter named value. The name of this hidden parameter never changes.  In addition to the value, the set block also receives the index against which the value is to be stored.  The index is received according to the parameter name specified in the type argument.

The set block must save the value against the index

As with a property, the code in the set block must save the value of the hidden parameter named value in an appropriate location if it is to be successfully returned later by the code in the get block.  Furthermore, for an indexer, the value must be stored according to the index so that it will be possible to retrieve and return it later on the basis of the same index.

The get block retrieves the value against the index

The get block in an indexer also receives an index, and should use that index to retrieve and return the value previously stored against that index.

Don't take the word index too literally

When thinking about indexers, we must be careful not to take the word index too literally.  We are accustomed to thinking of indices as positive integer values, usually beginning with zero and extending in a positive direction up to some maximum value with no missing integer values in between.  However, that concept is much too restrictive for indexers.

The index can be of any type

As mentioned in one of the introductory quotations earlier in this lesson, the index used for a C# indexer can be of any type.  Granted, some types may be better suited to the intended purpose than others, but there is no requirement that the indices used with a C# indexer be integer values. 

For example, the code in Listing C2 shows the use of a type argument of type string with an indexer.  What is important is that the programmer must be able to write code in the set and get blocks that will use the index, whatever its type, to accomplish the required objective.

An indexer with a type argument of string

The indexer shown in Listing C2, belongs to the same object, has a type argument of string, and has a return type of char.

  Hashtable ht = new Hashtable();

  public char this[string key]{
    set{  
      ht[key] = value;
    }//end set
    
    get{
      return (char)ht[key];
    }//end get
  }//end indexer 
    
}//end class IdxCls

Listing C2

Because the type of the index is string instead of int, a simple array is not a suitable data structure for storing and retrieving the indexed property values.  Therefore, an object of the Hashtable class was used for storing and retrieving the values.

What is a hashtable?

In case you aren't familiar with hashtables, according to Microsoft, a Hashtable object "Represents a collection of key-and-value pairs that are organized based on the hash code of the key." 

So, what does this mean?  As an example, the telephone directory is similar to a hashtable.  In the case of a telephone directory, the values (telephone numbers) are stored against unique keys (subscriber names).

A read-only collection

Since you are generally unable to add new items to the telephone directory, it might be regarded as a read-only collection of information.  To retrieve the telephone number of your friend, you first find your friend's name (key) in the telephone directory and then you read the telephone number (value).

Using a hashtable

The code in Listing C2 begins by instantiating a new object of the class named Hashtable.

The code in the set block stores the incoming value in the Hashtable object against the incoming key.

(Note that this looks a lot like the assignment of a value to an array element, except that key is not a numeric index.  Rather, in this case, key is a string.)

The code in the get block in Listing C2 uses the incoming key to retrieve the corresponding value from the Hashtable object and returns that value.

(All values retrieved from a Hashtable are retrieved as type Object.  Therefore, the code in the get block must cast the retrieved value to type char before returning it.)

Reflection

In a previous lesson, you learned how to perform reflection on a class in order to get information about its properties.  Figure 1 shows the property information produced by performing reflection on the class named IdxCls that we have been discussing

Number of Public Properties: 2
Descriptions of Properties:
Name: Item
Type: System.Double
Name: Item
Type: System.Char

Figure 1

(Note that the code used to perform the reflection is not shown here.)

Two properties

The reflection process is the final authority as to what is, and what is not, a property in C#.  As you can see in Figure 2, the reflection process identified each of the indexers discussed above as a property of the class named IdxCls.

No unique names

However, as we discussed earlier, the properties identified through reflection do not have unique names (simple properties do have unique names).  Rather, each indexer corresponds to a property with the common name Item

Property types

The types of the properties given in Figure 1 correspond to the return types of the indexers, and not to the type arguments of the indexers. 

The types don't seem to match

Recall that the first indexer had a return type of double while the second indexer had a return type of char

The corresponding types reflected in Figure 1 are System.Double, and System.Char.  Without going into a lot of detail at this point, suffice it to say that double is an alias for System.Double, and char is an alias for System.Char.  Thus the type information in Figure 1 matches what we already know to be true.

Indexers are properties

The most important thing to glean from Figure 1 is that while indexers are not commonly referred to as indexed properties in C# (as they are in Java), indexers are properties in C#.  Therefore, it would be entirely appropriate to refer to indexers as indexed properties in C#.

The driver class

Listing C3 shows the beginning of a class named Indexer01 used to exercise the class named IdxCls.

public class Indexer01{
  public static void Main(){
    IdxCls idxObj = new IdxCls();

Listing C3

The Main method in Listing C3 begins by instantiating a new object of the class named IdxCls, which contains two overloaded indexers.  As we learned above, one has a type argument of int while the other has a type argument of string.

Invoke the set blocks

Continuing with the Main method, the code in Listing C4 invokes the set blocks of each of the indexers.

    idxObj[3] = 93.5;//index int
    idxObj["abc"] = 'A';//index string

Listing C4

Note that the first statement in Listing C4 specifies an int index of 3, assigning a double value of 93.5 to the indexer.

The second statement in Listing C4 specifies a string index of "abc", assigning a char value of 'A' to the indexer.

"Recall what I said earlier about not taking the word index too literally in this case.  We normally wouldn't consider the string "abc" to be an index.  The word key would probably be more appropriate in this case."

Invoke the get blocks

Continuing with the Main method, the code in Listing C5 invokes the get block on each of the indexers. 

    System.Console.WriteLine(
                            idxObj[3]);
    System.Console.WriteLine(
                        idxObj["abc"]);

Listing C5

This code applies the same index values as the code in Listing C4 to retrieve and display the values stored in the two indexers.

No mention of set and get

The words set and get did not appear anywhere in the code in Listings C4 and C5, which invoked the indexer's set and get blocks.  In fact, there is nothing in this code to indicate that the set and get blocks are being invoked. (I knew they were being invoked because I wrote the code for the class named IdxCls.)

Simple storage and retrieval syntax

As is the case with properties, the syntax for storing a value in a C# indexer consists of an assignment operation.  However, unlike with a property, the left operand of the assignment operator specifies an index to indicate which element of the indexer will be used to store the value specified by the right operand.

Similarly, the syntax for a retrieval operation consists simply of calling out the name of the reference to the object containing the indexer, and providing the index of the element to be retrieved in square brackets.

Selection is based on type argument 

For both storage and retrieval, when the object contains two or more indexers (overloaded indexers), the selection among those indexers is made on the basis of the type used for the index. 

Same syntax as array and hashtable

As a practical matter, when the type of the index is int, the syntax of the storage operation is indistinguishable from a similar operation on an array, as shown in Listing C1. 

When the type of the index is string, the syntax of the retrieval operation is indistinguishable from a similar retrieval operation on a hashtable as shown in Listing C2.

The program output

If you compile and execute this program, the following two lines of text should be displayed on your screen:

93.5
A

A similar Java program

At this point, I am going to show and briefly discuss a Java program designed to use indexed properties to emulate the C# program discussed above.

My purpose is to illustrate the similarities between C# and Java, and perhaps to teach you some Java in the process.  If you have no interest in Java, just skip this part of the tutorial.

Before getting into the details of the program, let me say that if you compile and execute this Java program, the following two lines of text should be displayed on your screen:

93.5
A

This is exactly the same output produced by the C# program discussed above.

No limit on the number of indexed properties

As mentioned earlier, there is no limit on the number of indexed properties that can be defined in a Java class.  Furthermore, there is no requirement to use different index types for the different indexed properties.  All indexed properties in Java are indexed by type int.  As is the case for simple properties in Java, indexed properties in Java are identified by unique property names.

Java class IdxCls

Listing J1 begins the definition of the Java class named IdxCls

As is the case in the C# program discussed above, this class provides two indexed properties, one of type double and one of type char (Using the jargon from the earlier discussion on C# indexers, this refers to the return types of the indexed properties.)

class IdxCls{
  //Indexed property of type double
  double[] storFirst = new double[5];

  public void setFirst(
              int index, double value){
    //validating code if desired
    storFirst[index] = value;
  }//end setFirst

  public double getFirst(int index){
    //additional code if desired
    return storFirst[index];
  }//end getFirst

Listing J1

The indexed property named first

The code in Listing J1 shows all of the code required to establish one of the indexed properties.  This indexed property is named first

JavaBeans design patterns

Unlike C#, there is more than one way to establish an indexed property in Java.  The approach shown in Listing J1 is the simplest approach. 

This approach is based on JavaBeans design patterns.  Using design patterns, the code for an indexed property consists of a set method and a get method, with appropriate argument lists and return types.  (The set method would be absent for a read-only indexed property.)

Indexed property name and type

Methods meeting this simple design pattern are recognized by the Java Introspector as defining an indexed property. 

Note that the type of the property is determined by the second argument of the set method and the return type of the get method. 

The name of the property is determined by the word following set and get in each of the methods (with the case of the first character of that word flipped in most cases).

Java indexed properties have unique names

Thus, unlike C#, indexed properties in Java have unique names.  This is what makes it possible to define any number of indexed properties in a Java class, and to index them all as type int (no argument overloading is required).

Advantage of indexing by type int

Among other advantages, the fact that all indexed properties can be indexed as type int means that it is always possible to store the values in a simple array object of the same type as the type of the property.  It isn't necessary to use more complex data structures, such as hashtables, to store the values belonging to the indexed property. 

In Listing J1, the property values are stored in a simple array object of type double

In Listing J2, the property values are stored in a simple array object of type char(Recall that the C# program required a hashtable as the storage mechanism for the second indexer.)

Indexed property of type char

The code in Listing J2 defines an indexed property of type char in the same class as the indexed property discussed above.  The name of this indexed property is second.

  //Indexed property of type char
  char[] storSecond = new char[5];

  public void setSecond(
                int index, char value){
    //validating code if desired
    storSecond[index] = value;
  }//end setSecond

  public char getSecond(int index){
    //additional code if desired
    return storSecond[index];
  }//end getSecond

}//end class IdxCls

Listing J2

Except for the type of data involved and the name of the indexed property, there is nothing new here relative to the code in Listing J1.  Therefore, I won't discuss this code further.

The driver program

Listing J3 shows the beginning of a class designed to exercise the indexed properties discussed above.  Listing J3 shows the beginning of the class along with the beginning of the main method.

public class IndexProp01{
  public static void main(
                        String[] args){
    IdxCls idxObj = new IdxCls();

Listing J3

The code in the main method instantiates an object of the class named IdxCls, which provided two different indexed properties.  One of the properties is named first, and is of type double.  The other property is named second, and is of type char.

Invoke the setter methods

The code in Listing J4 invokes the appropriate setter method on each of the indexed properties, passing the index, along with the value to be stored as parameters to the methods.

    idxObj.setFirst(3,93.5);
    idxObj.setSecond(4,'A');

Listing J4

The code in Listing J4causes the double value of 93.5 to be stored at index 3 in the indexed property named first

The code in Listing J4 also causes the char value 'A' to be stored at index 4 in the indexed property named second

No confusion with array access

Because there is a method call involved, it is not possible to confuse either of these operations with a simple assignment to an array, as is the case in C#.

Invoke the getter methods

The code in Listing J5 invokes the appropriate getter method on each of the indexed properties, passing the appropriate index as a parameter to the method. 

Each method returns the value stored at the specified index.  These values are displayed on the computer screen.

    System.out.println(
                   idxObj.getFirst(3));
    System.out.println(
                  idxObj.getSecond(4));

Listing J5

Again, because there is a method call involved, it is not possible to confuse either of these operations with a simple retrieval from an array, as is the case in C#.

The output

As mentioned earlier, the code in Listing J5 causes the following two lines of output to be displayed on the screen:

93.5
A

This is the same output produced by the C# program discussed earlier.

Summary

Both C# and Java support the concept of indexed properties. An indexed property is a property that is capable of storing more than one value based on an index, and is capable of returning any one of those values based on an index.

Indexed properties in C# are commonly referred to simply as indexers, but the C# reflection process treats them as properties.

C# allows only one indexer of a given type argument per object.  However, C# indexers can be overloaded such that a given object can have more than one indexer so long as the type arguments for the different indexers are different.

The different overloaded indexers supported by a single C# object do not have unique names. They are distinguished from one another by their type arguments.

Java allows any number of indexed properties to be contained in a single object. Every indexed property in a Java object has a unique name and can have any return type.

For C# indexers having a type argument of type int the syntax of an access operation is indistinguishable from a similar access operation on an array.  When the type argument is string, the syntax of an access operation is indistinguishable from a similar access operation on a hashtable.

Because method invocations are required to access indexed properties in Java, there is no possibility that these operations will be confused with access operations on arrays and hashtables.

Access operations on C# indexers require less typing than access operations on indexed properties in Java.  Some programmers consider this to be an advantage.

On the other hand, this simplicity can sometimes lead to confusion for a person reading the C# code if that person doesn't have prior knowledge of the definition of the class containing the indexer.  As with simple properties, it is not possible to distinguish between indexer access and public field access solely on the basis of the source code being used to perform the access.  Prior knowledge of the class definition for the object being accessed is also required.

Complete Program Listings

A complete listing of the C# program is shown in Listing C6.  A complete listing of the Java program is shown in Listing J6.

 
/*File Indexer01.cs
Copyright 2002, R.G.Baldwin
Revised 10/30/02

This program illustrates overloaded
indexers.  Two indexers are defined in
the same class.  One indexer is of 
index type int and data type double.
The other indexer is of index type
string and data type char.

The output is:
  
93.5
A
**************************************/
using System.Collections;

public class Indexer01{
  public static void Main(){
    //Create new object containing two
    // different indexers.
    IdxCls idxObj = new IdxCls();

    //Invoke set blocks of both
    // indexers
    idxObj[3] = 93.5;//index int
    idxObj["abc"] = 'A';//index string

    //Invoke get blocks of both
    // indexers
    System.Console.WriteLine(
                            idxObj[3]);
    System.Console.WriteLine(
                        idxObj["abc"]);
  	
  }//end Main
}//end class Indexer01

//===================================//
/*
This class illustrates overloaded
 indexers.  The class provides two
 indexers, one of index type int and
 data type double while the other is
 of index type string and data type
 char.

Reflection on this class yields the
  following:
  	
Number of Public Properties: 2
Descriptions of Properties:
Name: Item
Type: System.Double
Name: Item
Type: System.Char
*/
class IdxCls{
  //Indexer for index type int
  double[] array =new double[5];
  public double this[int i]{
    set{  
      array[i] = value;
    }//end set
    
    get{
      return array[i];
    }//end get
  }//end indexer

  //Indexer for index type string
  Hashtable ht = new Hashtable();
  public char this[string key]{
    set{  
      ht[key] = value;
    }//end set
    
    get{
      return (char)ht[key];
    }//end get
  }//end indexer 
    
}//end class IdxCls

Listing C6

 

/*File IndexProp01.java
Copyright 2002, R.G.Baldwin
Revised 10/30/02

This program is designed to use indexed
properties to emulate the C# program
named Indexer01.cs insofar as it is
possible for indexed properties to
emulate indexers.

Two indexed properties are defined in
the same class.  One indexed property
is of type double.  The other indexed
property is of type char.

The output is:

93.5
A
**************************************/

public class IndexProp01{
  public static void main(
                        String[] args){
    //Create new object containing two
    // different indexed properties.
    // The two properties are named
    // first and second.
    IdxCls idxObj = new IdxCls();

    //Invoke setter methods on both
    // indexed properties.
    idxObj.setFirst(3,93.5);
    idxObj.setSecond(4,'A');

    //Invoke getter methods on both
    // indexed properties.
    System.out.println(
                   idxObj.getFirst(3));
    System.out.println(
                  idxObj.getSecond(4));

  }//end Main
}//end class IndexProp01

//===================================//
/*
This class provides two indexed
properties, one of type double and the
other of type char.
*/
class IdxCls{
  //Indexed property of type double
  double[] storFirst = new double[5];

  public void setFirst(
              int index, double value){
    storFirst[index] = value;
  }//end setFirst

  public double getFirst(int index){
    return storFirst[index];
  }//end getFirst
//-----------------------------------//

  //Indexed property of type char
  char[] storSecond = new char[5];

  public void setSecond(
                int index, char value){
    storSecond[index] = value;
  }//end setSecond

  public char getSecond(int index){
    return storSecond[index];
  }//end getSecond

}//end class IdxCls

Listing J6


Richard Baldwin is a college professor (at Austin Community College in Austin, Texas) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas.  He is the author of Baldwin's Programming Tutorials, which has gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

baldwin@DickBaldwin.com


Copyright 2002, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

-end-

 


© 1996, 1997, 1998, 1999, 2000, 2001, 2002 Richard G. Baldwin