Team LiB
Previous Section Next Section

Declaring Methods

Let's talk in a bit more detail about how we specify an object's behaviors. Recall from Chapter 3 that an object's methods may be thought of as services that the object can perform. In order for an object A to request some service of an object B, A needs to know the specific language with which to communicate with B. That is:

We take care of communicating these three aspects of each method by defining a method header. We must then program the behind-the-scenes logic for how B will perform the requested service, aka the method body.

Let's look at method headers first.

Method Headers

A method header is a formal specification (from a programming standpoint) of how that method is to be invoked. A method header consists of

  • A method's name

  • An optional list of comma-separated formal parameters (specifying their names and types) to be passed to the method enclosed in parentheses

  • A method's return type—that is, the data type of the information that is going to be passed back by object B to object A, if any, when the method is finished executing

As an example, here is a typical method header that we might define for the Student class:

                bool RegisterForCourse(string courseID, int secNo)
        return type    method name     comma-separated list of formal parameters,
                                       enclosed in parentheses
                                       (parentheses may be left empty)
Note?/td>

When casually referring to a method such as IsHonorsStudent in narrative text, many authors attach an empty set of parentheses, (), to the method name, for example, IsHonorsStudent(). This doesn't necessarily imply that the formal header has no arguments, however.

Passing Arguments to Methods

The purpose of passing arguments into a method is twofold:

  • To provide the object receiving the request with the (optional) "fuel" necessary to do its job, or

  • To (optionally) guide its behavior in some fashion

In the RegisterForCourse method shown previously, for example, it's necessary to tell the receiving Student object which course we want it to register for by passing in the course ID (for example, "MATH 101") and the section number (for example, 10, which happens to meet Monday nights from 8–10 p.m.).

Had we instead declared the RegisterForCourse method header with an empty parameter list:

bool RegisterForCourse()

the request would be ambiguous, because the receiving Student object would have no idea as to which course it's expected to register for.

Not all methods require such "fuel," however; some methods are able to produce results solely based on the information stored internally within an object, in which case no additional guidance is needed in the form of arguments. For example, the method

int GetAge()

is designed to be parameterless (i.e., takes no arguments) because a Student object can presumably tell us its age (based on its birthDate attribute, perhaps) without having to be given any qualifying information. Let's say, however, that we wanted a Student object to be able to report its age expressed either in years (rounded to the nearest year) or in months; in such a case, we might wish to declare the GetAge method as follows:

int GetAge(int ageType)

We would pass in an int(eger) argument to serve as a control flag for informing the Student object of how we want the answer to be returned; that is, we might program the GetAge method so that

  • If we pass in a value of 1, it means that we want the answer to be returned in terms of years (for example, 30).

  • If we pass in a value of 2, we want the answer to be returned in terms of months (for example, 30 ?12 = 360).

Note?/td>

An alternative way of handling the requirement to retrieve the age of a Student object in two different formats would be to define two separate methods, such as perhaps the following:

  int GetAgeInYears()
  int GetAgeInMonths()

but in object-oriented programming, it's common practice to control a method's behavior through the values (and types) of arguments.

Method Return Types

The RegisterForCourse method as previously declared is shown to have a return type of bool, which implies that this method will return one of the following two values:

  • A value of true, to signal "mission accomplished"—namely, that the Student object has successfully registered for the course that it was instructed to register for.

  • A value of false, to signal that the mission has failed for some reason: perhaps the desired section was full, or the student didn't meet the prerequisites of the course, or the requested course/section has been cancelled, etc.

Note?/td>

In Part Three of the book, you'll learn techniques for communicating and determining precisely why the mission has failed when we discuss exception handling.

Note that a method need not return anything—that is, it may go about its business silently, without reporting the outcome of its efforts. If so, it is declared to have a return type of void (another C# keyword).

Here are several additional examples of method headers that we might define for the Student class:

  • void SetName(string newName)

    This method requires one argument—a string representing the new name that we want this Student to assume—and performs "silently" by setting the Student's internal name attribute to whatever value is being passed into the method, returning no answer in response.

  • void SwitchMajor(string newDepartment, Professor newAdvisor)

    This method represents a request for a Student to change his or her major field of study, which involves designating both a new academic department (for example, "BIOLOGY") as well as a reference to the Professor object that is to serve as the student's advisor in this new department.

The preceding example demonstrates that we can declare parameters to be of any type, including user-defined types; the same is true for the return type of a method:

  • Professor GetAdvisor()

    This method is used to ask a Student object who its advisor is. Rather than merely returning the name of the advisor, the Student object returns a reference to the Professor object as a whole (as recorded in Student attribute facultyAdvisor).

    We'll see a need for returning handles to objects in this fashion shortly, when we explore how objects interact.

Note that a method can return only one result or answer, which may seem limiting. What if, for example, we want to ask a Student object for a list of all of the courses that the student has ever taken: must we ask for these one-by-one through multiple method calls? Fortunately not: the result handed back by a method can actually be a reference to an object of arbitrary complexity, including a special type of object called a collection that can contain multiple other objects. We'll talk about collections in more depth in Chapter 6.

Method Bodies

When we design and program a class in an OO language, we must not only provide headers for all of its methods, but also program the internal details of how each method should behave when it's invoked. These internal programming details, known as the method body, are enclosed within braces { } immediately following the method header, as follows:

public class Student
{
  // Attributes.
  double gpa;
  // other attributes omitted from this snippet ...
  // Here is a full-blown method, complete with a block of code to be executed
  // when the method is invoked.
  bool IsHonorsStudent() {
    // The programming details of what this method is to do
    // go between the braces ... this is the method body.

    // "gpa" is an attribute of the Student class, declared above.
    if (gpa >= 3.5) {
      return true; // true means "yes, this is an honors student"
    }
    else {
      return false; // false means "no, this isn't an honors student"
    }
  } // end of the method body

// etc.
}

Methods Implement Business Rules

The logic contained within a method body defines the business logic, also known as business rules, for an abstraction. In the IsHonorsStudent method shown in the preceding code, for example, there is a single business rule for determining whether or not a student is an honors student:

"If a student has a grade point average (GPA) of 3.5 or higher, then he/she is an honors student."

This rule is implemented in the preceding method through the use of a simple if test:

    if (gpa >= 3.5) {

If the business rules underlying this method were more complex—say, if the rules were as follows:

"In order for a student to be considered an honors student, the student must

  1. Have a grade point average (GPA) of 3.5 or higher;

  2. Have taken at least three courses;

  3. Have received no grade lower than ‘B’ in any of these courses."

then our method logic would of necessity be more complex:

bool IsHonorsStudent() {
    // Pseudocode.
    if ((gpa >= 3.5) &&
        (number of courses taken >= 3) &&
        (no grades lower than a B have been received)) {
      return true;
    }
    else {
      return false;
    }
  }

The return Statement

A return statement is a jump statement that is used to exit a method:

public void DoSomething() {
  // Pseudocode.
  do whatever ...

  return;
}

Whenever a return statement is executed, the method containing that return statement stops executing, and execution control returns to the code that invoked the method in the first place.

For methods with a return type of void, the return keyword can be used by itself, as a complete statement:

    return;

However, it turns out that for methods with a return type of void, use of a return statement is optional. If omitted, a return; statement is implied as the last line of the method. That is, the following two versions of method DoSomething are equivalent:

    public void DoSomething() {
      int x = 3;
      int y = 4;
      int z = x + y;
    }

and:

    public void DoSomething() {
      int x = 3;
      int y = 4;
      int z = x + y;
      return;
    }

The bodies of methods with a non-void return type must include at least one explicit return statement. The return keyword in such a case must be followed by an expression that evaluates to the proper type to match the method's return type:

    return expression;

For example, if a method is defined to have a return type of int, any of the following return statements would be acceptable:

    return 13;     // returning a constant integer value
    return x;      // assuming x is declared to be an int
    return x + y;  // assuming both x and y are declared to be ints

and so forth.

A method body is permitted to include more than one return statement if desired. For an example of a method containing multiple return statements, let's look once again at the IsHonorsStudent method discussed previously:

  bool IsHonorsStudent() {
    if (gpa >= 3.5) {
      return true; // first return statement
    }
    else {
      return false; // second return statement
    }
  }

Good programming practice, however, is to only have one return statement in a method, and to use a locally declared variable to capture the result that is to ultimately be returned. Here is an alternative version of the IsHonorsStudent method that observes this practice:


  bool IsHonorsStudent() {
    // Declare a local variable to keep track of the outcome; arbitrarily
    // initialize it to false.
    bool result = false;

    if (gpa >= 3.5) {
      result = true;
    }
    else {
      result = false;
    }

    // We now have a single return statement at the end of our method.
    return result;
  }

Naming Suggestions

Inventing descriptive names for both methods and parameters helps to make methods self-documenting. For example, the method header

void SwitchMajor(string newDepartment, Professor newAdvisor)

is much more self-explanatory than this alternative version:

void Switch(string d, Professor p)

In the latter case, we don't know what is being "switched," or what values for d and p would be relevant, without referring to the documentation for this method. Yet, to the compiler, both versions of the header are equally acceptable.

Note?/td>

Of course, one could argue that we'd always want to look at the documentation of a method anyway to determine its intended purpose and proper usage; but making the header self-documenting is even better.

C# method names are crafted using a style known as Pascal casing, wherein the first letter of the name is in uppercase; the first letter of each subsequent concatenated word in the variable name is in uppercase; and the remaining characters are in lowercase. As an example, SwitchMajor is an appropriate method name, whereas neither Switchmajor (lowercase "m") nor switchMajor (lowercase "s") would be appropriate.

Note?/td>

Recall from Chapter 1 that most variable names use what is known as Camel casing, which is the same as Pascal casing except for the fact that with Camel casing, the first letter is in lowercase, whereas with Pascal casing, the first letter is capitalized.


Team LiB
Previous Section Next Section