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.
October 31, 2002
C# Programming Notes # 108a
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.
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.
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.
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.
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.
Programming C# by Jesse Liberty
Here is what Jesse Liberty has to say about properties in his O'Reilly book entitled Programming C#:
C# In A Nutshell
C# Primer, A Practical Approach
What does Dick Baldwin have to say?
Liberty makes a good distinction between the return type of a C# indexer and the type argument of the indexer. He explains:
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.
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.
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.
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.
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.
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
(Note that the code used to perform the reflection is not shown here.)
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.
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.
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.
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.
Invoke the get blocks
Continuing with the Main method, the code in Listing C5 invokes the get block on each of the indexers.
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:
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:
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.)
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.
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.
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.
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.
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#.
As mentioned earlier, the code in Listing J5 causes the following two lines of output to be displayed on the screen:
This is the same output produced by the C# program discussed earlier.
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.
A complete listing of the C# program is shown in Listing C6. A complete listing of the Java program is shown in 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.
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.
© 1996, 1997, 1998, 1999, 2000, 2001, 2002 Richard G. Baldwin