Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'Data Access, Properties, and Methods

A complete PDF version of the text book is now available. The PDF version is an almost complete subset of the HTML version (where only a few, long program listings have been removed). See here.

19.  Indexers

From a notational point of view it is often attractive to access the data, encapsulated in a class or struct, via conventional array notation. Indexers are targeted to provide array notation on class instances and struct values.

Indexers can be understood as a specialized kind of properties, see Chapter 18. Both indexers and properties are classified as operations in this material, together with methods and other similar abstractions.

19.1 Indexers in C#19.3 Summary of indexers in C#
19.2 Associative Arrays
 

19.1.  Indexers in C#
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Indexers allow access to data in an object with use of array notation

The important benefit of indexers is the notation they make available to their clients.

Let us assume that v is a variable that holds a reference to an object obj. With use of methods we can access data in obj with v.method(parameters). In Chapter 18 we introduced properties and the property access notation v.property. We will now introduce the notation v[i], where i typically (but not necessarily) is some integer (index) value.

We will start with an artificial ABC example in Program 19.1 which tells how to define an indexer in a class that encapsulates three instance variables d, e, and f. The indexer is used in a client class in Program 19.2. The example introduces the indexer abstraction, but it is not a typical use of an indexer.

As it can be seen in Program 19.1 an indexer must be named "this". Like a property, it has a getter and a setter part.

The getter is activated when we encounter an expression a[i], where a is a variable of type A. The body of the getter determines the value of a[i].

The setter is activated when we encounter an assignment like a[i] = expression. The value of expression is bound to the implicit parameter named value. The body of the setter determines the effect on the instance variables in obj upon execution of a[i] = expression.

In Program 19.1 a[1] accesses the instance variable d, a[2] accesses the instance variable e, and a[3] accesses the instance variable f. This is not a typical arrangement, however. Most often, indexers are used to access members of a data collection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;

public class A {
  private double d, e, f;   

  public A(double v){
    d = e = f = v;          
  }

  public double this [int i]{
   get {                 
     switch (i){
       case 1: {return d;}     
       case 2: {return e;}     
       case 3: {return f;}     
       default: throw new Exception("Error");
     }
   }
   set {
     switch (i){
       case 1: {d = value; break;}   
       case 2: {e = value; break;}   
       case 3: {f = value; break;}   
       default: throw new Exception("Error");
    }
   }
  }             

  public override string ToString(){
    return "A: " + d + ", " + e + ", " + f;
  } 

}
Program 19.1    A Class A with an indexer.

Program 19.2 shows the indexer from Program 19.1 in action. First, in line 9, we illustrate the three setters, where a[i] occurs at the left-hand side of the assignment symbol. Following that, in line 11, we illustrate two getters. The output of Program 19.2 is shown in Listing 19.3 (only on web).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;

class B {
  
  public static void Main(){
    A a = new A(5);   
    double d;

    a[1] = 6; a[2] = 7.0; a[3] = 8.0;  

    d = a[1] + a[2];   
                       

    Console.WriteLine("a: {0}, d: {1}", a, d);
  }
}
Program 19.2    A client of A which uses the indexer of A.

1
a: A: 6, 7, 8, d: 13
Listing 19.3    Output from Main in class B.

As an additional example of indexers we will study the class BitArray, see Program 19.4, together with a client class. This example is only present in the web-version. This class is taken from the C# specification. It also appears in [Hejlsberg06]. To save some space, we do not show the bit array programs in the paper version. Please consult the web version.

Bitarray is a class that uses an array of integers for representation of a bit array. A bitarray is - quite naturally - an array in which the element type (conceptually) is a single bit. Each integer in C# holds 32 bits. Thus, if we request a bitarray of length 50 it will need only two integers to represent the 50 bits.

In Program 19.4 (only on web) the colored part shows the indexer. The indexer contains a getter and a setter. Together, they show how to get and set individual bits in the appropriate integer of the underlying integer array. This is all done by use of the bit-level operators of C#: the shifting operators << and >>, the complement operator ~, and bit-wise and (&) and or (|) operators. It takes some efforts to find out what actually happens in the indexer! If you need a little help, please read the text below Program 19.4.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// From the ECMA-334 C# Language specifications (section 17.8).
// Also in Hejlsberg et al., The C# Programming Language, 2ed.

using System;
public class BitArray   
{                       
   int[] bits;   
   int length;

   public BitArray(int length) {
      if (length < 0) throw new ArgumentException();
      bits = new int[((length - 1) >> 5) + 1];  
      this.length = length;                     
   }

   public int Length {
      get { return length; }
   }

   public bool this[int index] {                     
      get {                                          
         if (index < 0 || index >= length) {         
            throw new IndexOutOfRangeException();
         }
         return (bits[index >> 5] & 1 << index) != 0;  
      }
      set {                                          
         if (index < 0 || index >= length) {         
            throw new IndexOutOfRangeException();
         }
         if (value) {                                
            bits[index >> 5] |= 1 << index;          
         }
         else {                                      
            bits[index >> 5] &= ~(1 << index);
         }
      }
   }   
}
Program 19.4    The class BitArray.

Let me explain the expression bits[index >> 5] & 1 << index which occurs in line 25. Similar expressions (in terms of assignments) can be found in line 32 and 35. Due to the priorities of the involved operators (see Table 6.1) the expression should be parsed as (bits[index >> 5]) & (1 << index). The left operand of the & operator accesses the relevant integer in the bits array. The expression index >> 5 shifts the index five positions to the right, corresponding to an integer division with 32. The right operand constructs a mask. The operator & applies the mask.

Program 19.5 (only on web) shows a client of the BitArray, in which we benefit from the indexing notation introduced in in the BitArray class. The client program finds and counts the prime number less than given maximum. The bit array declared in line 9 is initially initialized with false values. This actually reflects that all numbers less than max are primes. In the two nested for loops this is adjusted, and the real primes are found and counted. In case that flags[i] is false, i is a prime number. The inner for loop eliminates all non-primes in which (the prime) i is a factor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// From the ECMA-334 C# Language specifications (section 17.8).
// Also in Hejlsberg et al., The C# Programming Language, 2ed.

using System; 

class CountPrimes
{
   static int Count(int max) {                 
      BitArray flags = new BitArray(max + 1);  
      int count = 1;                           
      for (int i = 2; i <= max; i++) {         
         if (!flags[i]) {
            for (int j = i * 2; j <= max; j += i) flags[j] = true;
            count++;                           
         }
      }
      return count;
   }
   static void Main(string[] args) {           
      int max = int.Parse(args[0]);            
      int count = Count(max);
      Console.WriteLine("Found {0} primes between 1 and {1}", count, max);
   }
}
Program 19.5    A client of class BitArray.

Let us summarize in the following way. Indexers in C# provide convenient array-like notation v[i,j] instead of method notation such as v.Get(i,j) and v.Set(i,j,value). In some programs this may be felt as a significant notational advantage. In other program, it does not really matter. Like properties, indexers are not strictly necessary. It will always be possible to replace an indexer or a property with one or two methods.

 

19.2.  Associative Arrays
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

In Section 19.1 we showed how to index an object with a single integer index. In this section we will demonstrate that the indexing value can have an arbitrary type. Thus, the type of obj in a[obj] can be an arbitrary type in C#, for instance a type we program ourselves.

An associative array is an array which allows indexing by means of arbitrary objects, not just integers

An associative arrays maps a set of objects (the indexing objects, keys) to another set of objects (the element objects).

In Program 19.6 we illustrate how to index instances of class A with strings instead of integers. If you understood Program 19.1 and Program 19.2 it will also be easy to understand Program 19.6 and Program 19.7. Notice in this context that C#, very conveniently, allows switch control structures to switch on strings. The program output is shown in Listing 19.8 (only on web).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;

public class A {
  private double d, e, f;

  public A(double v){
    d = e = f = v;
  }

  public double this [string str]{
   get {
     switch (str){
       case "d": {return d;}
       case "e": {return e;}
       case "f": {return f;}
       default: throw new Exception("Error");
     }
   }
   set {
     switch (str){
       case "d": {d = value; break;}
       case "e": {e = value; break;}
       case "f": {f = value; break;}
       default: throw new Exception("Error");
    }
   }
  }   

  public override string ToString(){
    return "A: " + d + ", " + e + ", " + f;
  } 

}
Program 19.6    The class A indexed by a string.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;

class B {
  
  public static void Main(){
    A a = new A(5); 
    double d;

    a["d"] = 6; a["e"] = 7.0; a["f"] = 8.0;

    d = a["e"] + a["f"];   // corresponds to d = a.d + a.e 
                           // in case d and e had been public

    Console.WriteLine("a: {0}, d: {1}", a, d);
  }
}
Program 19.7    A client of A which uses the string indexing of A.

1
a: A: 6, 7, 8, d: 15
Listing 19.8    Output from Main in class B.

We have seen that it makes sense to index with strings, and more generally with an arbitrary instance of a class. In fact, it is possible to base the indexing on two or more objects. This is, of course, important if we index multi-dimensional data structures.

Associative arrays are in C# implemented by means of hashtables in dictionaries

In the lecture about collections, see Chapter 46, we will see how to make use so-called dictionaries (typically implemented as hash tables) for efficient data structures that map a set of objects to another set of objects. Indexers, as discussed in this section chapter, provide a convenient surface notation to deal with such dictionaries. In Section 46.2 the indexer prescribed by the generic interface IDictionary<K,V> accesses objects of type V via an index of type K.

 

19.3.  Summary of indexers in C#
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Here follows a syntax diagram of indexers:


 modifiers return-type this[formal-parameter-list]
  get {body-of-get}
  set {body-of-set}
}
Syntax 19.1    The syntax of a C# indexer

It is similar to the syntax diagram of properties, as shown in Syntax 18.1

The main characteristics of indexers are as follows:

  • Provide for indexed read-only, write-only, or read-write access to data in objects

  • Indexers can only be instance members - not static

  • The indexing can be based on one, two or more formal parameters

  • Indexers can be overloaded

  • Indexers should be without unnecessary side-effects

  • Indexers should be fast

 

19.4.  References
[Hejlsberg06]Anders Hejlsberg, Scott Wiltamuth and Peter Golde, The C# Programming Language. Addison-Wesley, 2006.

Generated: Monday February 7, 2011, 12:15:33
Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'