Chapter 5: Advanced C# Class Construction Techniques

Posted by Apress Free Book | C# Language January 13, 2009
This chapter rounds out your introduction to the core aspects of the C# language by examining a number of advanced (but extremely useful) syntactic constructs. To begin, you learn how to construct and use an indexer method.

This chapter rounds out your introduction to the core aspects of the C# language by examining a number of advanced (but extremely useful) syntactic constructs. To begin, you learn how to construct and use an indexer method. This C# mechanism enables you to build custom types, which exposes internal subtypes using the familiar bracket operator (i.e., []). If you have a C++ background, you will find that creating a C# indexer method is analogous to overloading the [] operator on a C++ class. Once you learn how to build an indexer, you then examine how to overload various operators (+, -, <, > and so forth) for a custom C# type.

This chapter then examines three techniques that enable the objects in your system to engage in bidirectional communications. First, you learn about the C# "delegate" keyword, which is little more than a type-safe function pointer. Once you learn how to create and manipulate delegates, you are in a perfect position to investigate the .NET event protocol, which is based on the delegation model. Finally, you discover how the use of custom interfaces can also enable bidirectional communications (which should ring a bell for those coming from a COM background).

I wrap up by examining how you can document your types using XML attributes, and how the Visual Studio.NET IDE automatically generates Web-based documentation for your projects. Although this might not qualify as a truly "advanced" technique, it is a high note on which to end the chapter.

Building a Custom Indexer

At this point, you should feel confident building C# types using traditional OOP (refer to Chapter 3) as well as interface-based programming techniques (refer to Chapter 4). In this chapter, I take some time to examine some additional aspects of C# that you may not be readily failure with, beginning with the concept of an indexer. Most programmers (such as yourself) are very familiar with the process of accessing discrete items held within a standard array using the index (aka bracket) operator:

// Declare an array of integers.
int[] myInts = {10, 9, 100, 432, 9874};
// Use the [] operator to access each element.
for(int j = 0; j < myInts.Length; j++)
Console.WriteLine("Index {0} = {1}", j, myInts[j] j]);

The C# language supports the capability to build custom classes that may be indexed just like an array of intrinsic types. It should be no big surprise that the method that provides the capability to access items in this manner is termed an "indexer." Before exploring how to create such a construct, let's begin by seeing one in action. Assume you have added support for an indexer method to the Cars container developed in the previous chapter. Observe the following usage:

// Indexers allow you to access items in an array array-like fashion.

public class CarApp
    public static void Main()
        // Assume the Cars type has an indexer method.
        Cars carLot = new Cars();
        // Make some cars and add them to the car lot.
        carLot[0] = new Car("FeeFee", 200, 0);
        carLot[1] = new Car("Clunker", 90, 0);
        carLot[2] = new Car("Zippy", 30, 0);
        // Now obtain and display each item.
        for (int i = 0; i < 3; i++)
            Console.WriteLine("Car number {0}:", i);
            Console.WriteLine("Name: {0}", carLot[i].PetName);
            Console.WriteLine("Max speed: {0}", carLot[i].MaxSpeed);

A test run would look something like Figure 5-1.

Figure 5-1. Accessing cars using an indexer

As you can see, indexers behave much like a custom collection supporting the IEnumerator and IEnumerable interfaces. The only major difference is that rather than A Comprehensive Guide to C# and the .NET Platform, ©2001 Andrew Troelsen (Apress, ISBN: 1-893115-59-3) p. 5-3 accessing the contents using interface references, you are able to manipulate the internal collection of automobiles just like a standard array.

Now for the big question: How do you configure the Cars class (or any class) to do so? The indexer itself is represented as a slightly mangled C# property. In its simplest form, an indexer is created using the this[] syntax:

// Add the indexer to the existing class definition.

public class Cars : IEnumerator, IEnumerable
    // Let's rollback to the basics and simply make use of a standard array
    // to contain the cars. You are free to use an ArrayList if you desire…
    private Car[] carArray;
    public Cars()
        carArray = new Car[10];
    // The indexer returns a Car based on a numerical index.
    public Car this[int pos]
        // Accessor returns an item in the array.
            if (pos < 0 || pos > 10)
                throw new IndexOutOfRangeException("Out of range!");
                return (carArray[pos]);
        // Mutator populates the array.
        set { carArray[pos] = value; }

Beyond the use of the "this" keyword, the indexer looks just like any other C# property declaration. Do be aware that indexers do not provide any array-like
functionality beyond the use of the subscript operator. In other words, the object user cannot write code such as:

// Use System.Array.Length? Nope!
Console.WriteLine("Cars in stock: {0}", carLot.Length);

To support this functionality, you would need to add your own Length property to the Cars type, and delegate accordingly:

public class Cars
    // Containment / delegation in action once again.
    public int Length() { /* figure out number of non-null entries in array. */}

However, if you are in need of this functionality, you will find your task will be much easier if you make direct use of one of the System.Collections types to hold your internal items, rather than a simple array.

SOURCE CODE The Indexer project is located under the Chapter 5 subdirectory.

Total Pages : 11 12345