Chapter 8. Inside Methods

In Chapter 7, you saw that classes consist of fields and methods . Fields hold the state of the object, and methods define the object’s behavior.

In this chapter, you’ll explore how methods work in more detail. You’ve already seen how to create methods, and in this chapter, you’ll learn about method overloading , a technique that allows you to create more than one method with the same name. This enables your clients to invoke the method with different parameter types.

This chapter also introduces properties. To clients of your class, properties look like member variables, but properties are implemented as methods. This allows you to maintain good data-hiding, while providing your clients with convenient access to the state of your class.

Chapter 7 described the difference between value types (such as int and long) and reference types. The most common value types are the “built-in” or “primitive” types (as well as structs), while the most common reference types are the user-defined types. This chapter explores the implications of passing value types to methods and shows how you can pass value types by reference, allowing the called method to act on the original object in the calling method.

Overloading Methods

Often you’ll want to have more than one method with the same name. The most common example of this is to have more than one constructor with the same name, which allows you to create the object with different types of parameters, or a different number of parameters. For example, if you were creating a Time object, you might have circumstances where you want to create the Time object by passing in the date, hours, minutes, and seconds. Other times, you might want to create a Time object by passing in an existing Time object. Still other times, you might want to pass in just a date, without hours and minutes. Overloading the constructor allows you to provide these various options.

Chapter 7 explained that your constructor is automatically invoked when your object is created. Let’s return to the Time class created in that chapter, to the client who could create a Time object by passing in a DateTime object to the constructor.

It would be convenient also to allow the client to create a new Time object by passing in year, month, date, hour, minute, and second values. Some clients might prefer one or the other constructor; you can provide both, and the client can decide which better fits the situation.

In order to overload your constructor, you must make sure that each constructor has a unique signature. The signature of a method is composed of its name and its parameter list. Two methods differ in their signatures if they have different names or different parameter lists. Parameter lists can differ by having different numbers or types of parameters. The following four lines of code show how you might distinguish methods by signature:

    void MyMethod(int p1);
    void MyMethod(int p1, int p2);    // different number
    void MyMethod(int p1, string s1); // different types
    void SomeMethod(int p1);          // different name

The first three methods are all overloads of the MyMethod( ) method. The first differs from the second and third in the number of parameters. The second closely resembles the third version, but the second parameter in each is a different type. In the second method, the second parameter (p2) is an integer; in the third method, the second parameter (s1) is a string. These changes to the number or type of parameters are sufficient changes in the signature to allow the compiler to distinguish the methods.

The fourth method differs from the other three methods by having a different name. This is not method overloading, just different methods, but it illustrates that two methods can have the same number and type of parameters if they have different names. Thus, the fourth method and the first have the same parameter list, but their names are different.

A class can have any number of methods, as long as each one’s signature differs from that of all the others. Example 8-1 illustrates a Time class with two constructors : one that takes a DateTime object and one that takes six integers.

The output looks like this:

    7/10/2008 16:17:32
    11/18/2000 11:3:30

If a function’s signature consisted only of the function name, the compiler would not know which constructors to call when constructing the new Time objects, time1 and time2. However, because the signature includes the parameters and their types, the compiler is able to match the constructor call for time1 with the constructor whose signature requires a DateTime object:

    System.DateTime currentTime = System.DateTime.Now;
    Time time1 = new Time(currentTime);
    public Time(System.DateTime dt)

Likewise, the compiler is able to associate the time2 constructor call with the constructor whose signature specifies six integer arguments.

    Time time2 = new Time(2000,11,18,11,03,30);
    public Time(int Year, int Month, int Date, int Hour, int Minute, int Second)

It is generally desirable to designate the member variables of a class as private. This means that only member methods of that class can access their value. When you prevent methods outside the class from directly accessing member variables, you’re enforcing data hiding , which is part of the encapsulation of a class.

Object-oriented programmers are told that member variables should be private. That’s fine, but how do you provide access to this data to your clients? The answer for C# programmers is properties. Properties allow clients to access class state as if they were accessing member fields directly, while actually implementing that access through a class method.

This solution is ideal. The client wants direct access to the state of the object. The class designer, however, wants to hide the internal state of the class in class fields and provide indirect access through a method. The property provides both the illusion of direct access for the client, and the reality of indirect access for the class developer.

By decoupling the class state from the method that accesses that state, the designer is free to change the internal state of the object as needed. When the Time class is first created, the Hour value might be stored as a member variable. When the class is redesigned, the Hour value might be computed or retrieved from a database. If the client had direct access to the original Hour member variable, changing how that value is resolved would break the client. By decoupling and forcing the client to go through a property, the Time class can change how it manages its internal state without breaking client code.

In short, properties provide the data hiding required by good object-oriented design. Example 8-2 creates a property called Hour, which is then discussed in the paragraphs that follow.

The output should look something like this:

    Time : 7/10/2008 12:7:43
    Retrieved the hour: 12
    Updated the hour: 13

You create a property by writing the property type and name followed by a pair of braces. Within the braces, you can declare the get and set accessors. These accessors are very similar to methods, but they are actually part of the property itself. The purpose of these accessors is to provide the client with simple ways to retrieve and change the value of the private member hour, as you’ll see.

Neither of these accessors has explicit parameters , though the set accessor has an implicit parameter called value, which is used to set the value of the member variable.

In Example 8-2, the declaration of the Hour property creates both get and set accessors:

    public int Hour
    {
         get
         {
            return hour;
         }

     set
         {
            hour = value;
         }
    }

Each accessor has an accessor-body, which does the work of retrieving or setting the property value. The property value might be stored in a database (in which case, the accessor would do whatever work is needed to interact with the database), or it might just be stored in a private member variable (in this case, hour):

    private int hour;

Methods can return only a single value, but this isn’t always convenient. Let’s return to the Time class. It would be great to create a GetTime( ) method to return the hour, minutes, and seconds. You can’t return all three of these as return values, but perhaps you can pass in three parameters, let the GetTime( ) method modify the parameters, and then examine the result in the calling method—in this case, Run( ) . Example 8-3 is a first attempt.

The output will look something like this:

    7/1/2008 12:22:19Current time: 0:0:0

Notice that the “Current time” in the output is 0:0:0. Clearly, this first attempt did not work. The problem is with the parameters. You pass in three integer parameters to GetTime( ), and you modify the parameters in GetTime( ), but when the values are accessed back in Run( ), they are unchanged. This is because integers are value types.

Passing Value Types by Reference

As discussed in Chapter 7, C# divides the world of types into value types and reference types. All intrinsic types (such as int and long) are value types. Instances of classes (objects) are reference types.

When you pass a value type (such as an int) into a method, a copy is made. When you make changes to the parameter, you make changes to the copy. Back in the Run( ) method, the original integer variables—theHour, theMinute, and theSecond—are unaffected by the changes made in GetTime( ).

What you need is a way to pass in the integer parameters by reference so that changes made in the method are made to the original object in the calling method. When you pass an object by reference, the parameter refers to the same object. Thus when you make changes in GetTime( ), the changes are also made to the original variables in Run( ).

This requires two small modifications to the code in Example 8-3. First, change the parameters of the GetTime( ) method to indicate that the parameters are ref (reference) parameters:

    public void GetTime(
       ref int theHour,
       ref int theMinute,
       ref int theSecond )
    {
       theHour = Hour;
       theMinute = Minute;
       theSecond = Second;
    }

Second, modify the call to GetTime( ) to pass the arguments as references:

    t.GetTime(ref theHour, ref theMinute, ref theSecond);

These changes are shown in Example 8-4.

This time, the output looks like this:

    7/1/2008 12:25:41Current time: 12:25:41

The results now show the correct time.

By declaring these parameters to be ref parameters, you instruct the compiler to pass them by reference. Instead of a copy being made, the parameters in GetTime( ) are references to the corresponding variables (theHour, theMinute, theSecond) that were created in Run( ). When you change these values in GetTime( ), the change is reflected in Run( ).

Keep in mind that ref parameters are references to the actual original value—it is as if you said, “here, work on this one.” Conversely, value parameters are copies—it is as if you said, “here, work on one just like this.”

As noted in Chapter 4, C# imposes definite assignment, which requires that all variables be assigned a value before they are used. In Example 8-4, you initialize theHour, theMinute, and theSecond before you pass them as parameters to GetTime( ), yet the initialization merely sets their values to 0 before they are passed to the method:

    int theHour = 0;
    int theMinute = 0;
    int theSecond = 0;
    t.GetTime( ref theHour, ref theMinute, ref theSecond);

It seems silly to initialize these values because you immediately pass them by reference into GetTime( ) where they’ll be changed, but if you don’t, the following compiler errors are reported:

    Use of unassigned local variable 'theHour'
    Use of unassigned local variable 'theMinute'
    Use of unassigned local variable 'theSecond'

C# provides the out modifier for situations like this, in which initializing a parameter is only a formality. The out modifier removes the requirement that a reference parameter be initialized. The parameters to GetTime( ), for example, provide no information to the method; they are simply a mechanism for getting information out of it. Thus, by marking all three as out parameters using the out keyword, you eliminate the need to initialize them outside the method.

Within the called method, the out parameters must be assigned a value before the method returns. Here are the altered parameter declarations for GetTime( ):

    public void GetTime(
       out int theHour,
       out int theMinute,
       out int theSecond )
    {
       theHour = Hour;
       theMinute = Minute;
       theSecond = Second;
    }

Here is the new invocation of the method in Main( ):

    int theHour;
    int theMinute;
    int theSecond;
    t.GetTime( out theHour, out theMinute, out theSecond);

The keyword out implies the same semantics as the keyword ref, except that it also allows you to use the variable without first initializing it in the calling method.