You will learn about the following in this chapter:
How to implement and work with properties
How to implement and work with indexers
Indexers and overloading
User-defined operator overloading
User-defined conversions
Nested types
The constructs (properties, indexers, operator overloading, and user-defined conversions) discussed in this chapter do not enable us to write programs that could not easily be implemented by using the familiar method construct. In fact, some of these constructs are compiled into MSIL code that looks nearly identical to that generated for methods. Nevertheless, if we implement them when suitable in our classes, they allow the users of these classes to write cleaner and more intuitive code.
Properties provide elegant and robust support for the encapsulation principle introduced in Chapter 3, "A Guided Tour through C#: Part I." They allow the users of your classes to access private instance variables as if they were public, without using the cumbersome accessor and mutator methods presented earlier. This is achieved without jeopardizing the protection and data hiding that is required for private instance variables, because properties are implemented in a method-like fashion inside the class.
Indexers allow you to access a collection (for example an array) kept within an object from outside this object as if the object itself was an array. So, instead of using a method to access an array of, say, rainfall data inside an object called rainfallInCalifornia as shown here
rainfallInCalifornia.GetRainfallInMonth(6)
indexers allow you to simply use the square brackets used with arrays as in the following line:
rainfallInCalifornia[6]
Through operator overloading, you can apply operators like + and - on your own user-defined types instead of using the, at times, ugly looking methods. For example, you might have written a Fraction class to represent fractions. Without operator overloading, you would likely write a method called Add to add two fraction objects together (here called thisFraction and thatFraction) and assign the result to a third fraction object (called sumFraction). This would look like the following line:
sumFraction = thisFraction.Add(thatFraction);
Instead of operator overloading, let us write the cleaner looking and more intuitive line
sumFraction = thisFraction + thatFraction;
User-defined conversions also provide elegant substitutes for methods. Suppose you have written two classes called TempFahrenheit and TempCelsius both representing temperatures. You could then write a conversion method in the TempFahrenheit class called ConvertToCelsius. This would allow you to convert from the object myTempFahrenheit to myTempCelsius by writing the following:
myTempCelsius = myTempFahrenheit.ConvertToCelsius();
Alternatively, user-defined conversions allow us to perform an implicit conversion between the two types simply by writing the following:
myTempCelsius = myTempFahrenheit;
Of all the language elements in C#, the constructs presented in this chapter can easily be overused and misused, resulting in cryptic and unclear code. So when you read through the chapter, make sure you understand the problems each construct was meant to solve and even more important not meant to solve. This information is often presented in Tip and Common Pitfall boxes.
The chapter ends with a discussion of the last main class member category (the other two were data and function members) called nested types. Nested types allow us to insert type definitions in a class block. As a result, we can avoid exposing types to the outside world, which are only of interest to one particular class.