Team LiB
Previous Section Next Section

Method Invocation and Dot Notation

Now that we understand how methods are formally specified, how do we represent in code that a method is being invoked on an object? We create what is known as a message. A message is an expression formed by following the name of the reference variable representing the object that is to receive the message with a "dot" (period) followed by a method (function) call. For example:

  // Instantiate a Student object.
  Student x = new Student();

  // Send a message to (call a method on) Student object x, asking it to
  // register for course MATH 101, section 10.
  x.RegisterForCourse("MATH 101", 10); // This is a message!

Because we are using a "dot" to "glue" the reference variable to the method header, this is informally known as dot notation. And, in referring to the following logic:

x.RegisterForCourse("MATH 101", 10);

we can describe this as either "calling a method on object x" or as "sending a message to object x."

Note?/td>

In C#, we formally refer to the use of dot notation as "qualifying a (method) name," but we'll stick with the informal, generally accepted OOP nomenclature of "dot notation" throughout this book.

Arguments vs. Parameters

A bit of terminology: to make sure that everyone understands the difference between "parameters" and "arguments," both of which are generic programming language terms, let's define both terms here.

A parameter is a locally scoped variable, declared in a method header, that temporarily comes into existence while a method is executing. For example, when the method

  public void Foo(int bar) { ... }

is invoked, a variable named bar of type int temporarily comes into existence and is initialized with the value of the argument that is passed in when the method is invoked from client code:

  // Here, we're invoking the Foo method, passing in an argument value of 3.
  x.Foo(3);

In this case, parameter bar assumes the argument value 3.

While the method is executing, it can then use the parameter as a variable as it sees fit:

  public void Foo(int bar) {
    // Use the value of bar as appropriate.
    if (bar > 17) {
    // Pseudocode.
    Do something nifty!

  }
  // "bar" goes out of scope here ... it's only defined within the Foo method.

The parameter bar ceases to exist—i.e., goes out of scope (a concept we discussed in Chapter 1)—when the method exits.

Although the two terms "parameter" and "argument" are frequently and casually used interchangeably, the bottom line is that they are different concepts: namely, arguments are values; parameters are variables.

Objects As the Context for Method Invocation

In an object-oriented programming language, an object serves as the context for a method call. We can thus think of the notation "x.method_call()" as "talking to object x"; specifically, "talking to object x to request it to perform a particular method." Let's use a simple example to illustrate this point.

With respect to household chores, a person is capable of

  • Taking out the trash

  • Mowing the lawn

  • Washing the dishes

Expressing this abstraction as C# code:

    public class Person {
      // Attributes omitted from this example.

      // Methods:
      public void TakeOutTheTrash() { ... }
      public void MowTheLawn() { ... }
      public void WashTheDishes() { ... }
    }

We decide that we want our teenaged sons Larry, Moe, and Curley to each do one of these three chores. How would we ask them to do this? If we were to say

  • "Please wash the dishes."

  • "Please take out the trash."

  • "Please mow the lawn."

chances are that none of the chores would get done, because we haven't tasked a specific son with fulfilling any of these requests! Larry, Moe, and Curley will probably all stay glued to the TV, because none of them will acknowledge that a request has been directed toward them.

On the other hand, if we were to instead say

  • "Larry, please wash the dishes."

  • "Moe, please take out the trash."

  • "Curley, please mow the lawn."

we'd be assigning each task to a specific son; again, using C# syntax, this might be expressed as follows:

    // We create three Person objects/instances:
    Person larry = new Person();
    Person moe = new Person();
    Person curley = new Person();

    // We send a message to each, indicating the service that we wish
    // each of them to perform:
    larry.WashTheDishes();
    moe.TakeOutTheTrash();
    curley.MowTheLawn();

By applying each method call to a different "son" (Person object reference), there is no ambiguity as to which object is being asked to perform which service.

Note?/td>

We'll learn in Chapter 7 that a class as a whole can also be the target of a method call for a special type of method known as a static method.

Assuming that WashTheDishes is a Person method as defined previously, the following code won't compile in C# (or, for that matter, in any OOPL):

public class BadCode
{
  static void Main() {
    // This next line won't compile -- where's the "dot"?
    WashTheDishes();
  }
}

because the compiler would expect the call to WashTheDishes(), as a Person method, to be associated with a particular Person object via dot notation; the following error message would result:

  error CS0120: An object reference is required for the non-static field,
    method, or property 'Person.WashTheDishes()'

However, in a non- OOPL language like C, there are no objects or classes, and so functions are always called "in a vacuum."

C# Expressions, Revisited

When we defined the term simple expression in Chapter 1, there was one form of expression that we omitted, because we hadn't yet talked about objects: namely, messages. We've repeated our list of what constitutes C# expressions here, adding messages to the mix:

  • A constant: 7, false

  • A char(acter) literal: ‘A’, ‘&’

  • A string literal: "foo"

  • The name of any variable declared to be of one of the predefined types that we've seen so far: myString, x

  • A message: z.length()

  • Any two of the preceding that are combined with one of the C# binary operators: x + 2

  • Any one of the preceding that is modified by one of the C# unary operators: i++

  • Any of the preceding simple expressions enclosed in parentheses: (x + 2)

Note?/td>

In Chapter 13, you'll learn about one more type of expression: a "chain" of two or more messages, concatenated by dots (.): e.g., x.GetAdvisor().GetName();.

The type of a "message expression" is, by definition, the type of result that the method returns when executed. For example, if RegisterForCourse is a method with a return type of bool, then the expression s.RegisterForCourse() is said to be an expression of type bool.

Capturing the Return Value from a Method Call

In an earlier example, although we declared the Student class's RegisterForCourse method to have a return type of bool:

bool RegisterForCourse(string courseId, int sectionNumber)

we didn't capture the returned bool value when we invoked the method:

x.RegisterForCourse("MATH 101", 10);

Whenever we invoke a non-void method, it's up to us whether to ignore or respond to the value that it returns. If we wish to respond to the returned value, we can optionally capture the result in a specific variable:

boolean outcome;
outcome = x.registerForCourse("MATH 101", 10);

if (!outcome) {
  action to be taken if registration failed ...
}

If we only plan on using the returned value once, however, then going to the trouble of declaring an explicit variable such as outcome to capture the result is overkill. We can instead react to the result simply by "nesting" a message (which we learned a moment ago is considered to be a valid expression type) within a more complex statement. For example, we can rewrite the preceding code snippet to eliminate the variable outcome as follows:

// An if expression must evaluate to a Boolean result; the
// RegisterForCourse method does indeed return a Boolean value, however, and so
// the expression (message) enclosed within parentheses represents valid syntax.
if (!(x.RegisterForCourse("MATH 101", 10))) {
  action to be taken if registration failed ...
}
Note?/td>

In fact, we use the "message-as-expression" syntax liberally when developing object-oriented applications; for example, when returning values from methods:

    public string GetAdvisorName() {
      return advisor.GetName();
    }

or printing to the console:

    Console.WriteLine("The student's gpa is: " + s.GetGPA());

etc. This is one of the aspects of OOP that seems to take the most getting used to for folks who are just getting started with learning the object-oriented paradigm.

Method Signatures

We've already learned that a method header consists of the method's return type, name, and formal parameter list:

void SwitchMajor(string newDepartment, Professor newAdvisor)

From the standpoint of the code used to invoke a method on an object, however, the return type and parameter names aren't immediately evident upon inspection:


    Student s = new Student();
    Professor p = new Professor();

    // Details omitted ...

    s.SwitchMajor("MATH", p);

We can infer from inspecting this code that

  • SwitchMajor is a method defined for the Student class, because s is a Student and we're invoking SwitchMajor on s.

  • The SwitchMajor method requires two arguments of type string and Professor, respectively, because that's what we're passing in.

However, we can't see how the formal parameters were named in the corresponding method header, nor can we tell whether the method returns a result or not and, if so, what type of result it returns, because it may be returning a result that we've simply chosen to ignore.

For this reason, we refer to a method's signature as those aspects of a method header that are "discoverable" from the perspective of the code used to invoke the method: namely, the method's name, and the order, types, and number of arguments being passed into the method, but excluding the parameter names and method return type.

Let's introduce one more bit of terminology: we've coined the informal terminology "argument signature" to refer to that subset of a method's signature consisting of the order, types, and number of arguments comprising a method signature, but not their specific names.

Some examples of method headers and their corresponding method/argument signatures are shown here:

  • Method header: int GetAge(bool ageType)

    • Method signature: GetAge(bool)

    • Argument signature: (bool)

  • Method header: void SwitchMajor(string newDepartment, Professor newAdvisor)

    • Method signature: SwitchMajor(string, Professor)

    • Argument signature: (string, Professor)

  • Method header: string GetName()

    • Method signature: GetName()

    • Argument signature: ()

Note?/td>

"Argument signature" isn't an industry standard term, but one that we nonetheless find very useful.We'll find the notion of argument signatures to be particularly handy when we discuss the concept of overloading in Chapter 5.%

Message Passing Between Objects

Let's now look at a message passing example involving two objects. Assume that we have two classes defined—Student and Course—and that the methods listed in Table 4-1 are defined on each.

Table 4-1: Student and Course class methods

Method

Description

Student Method:
bool SuccessfullyCompleted(Course c)

Given a reference c to a particular Course object, we're asking the Student object receiving this message to confirm that they have indeed taken the course in question and received a passing grade.

Course Method:
bool Register(Student s)

Given a reference s to a particular Student object, we are asking the Course object receiving this message to do whatever is necessary to register the student. In this case, we expect the Course to ultimately respond true or false to indicate success or failure of the registration request.

Figure 4-2 reflects one possible message interchange between a Course object c and a Student object s; each numbered step in the diagram is narrated in the text that follows.

Click To expand
Figure 4-2: Message passing between a Student and Course object

(Please refer back to this diagram when reading through steps 1 through 4.)

  1. A Course object c receives the message

                c.Register(s);
    

    where s represents a particular Student object. (For now, we won't worry about the origin of this message; it was most likely triggered by a user's interaction with the SRS GUI. We'll see the complete code context of how all of these messages are issued later in this chapter, in the section entitled "Objects As Clients and Suppliers.")

  2. In order for Course object c to officially determine whether or not s should be permitted to register, c sends the message

                s.SuccessfullyCompleted(c2);
    

    to Student s, where c2 represents a reference to a different Course object that happens to be a prerequisite of Course c. (Don't worry about how Course c knows that c2 is one of its prerequisites; this involves interacting with c's internal prerequisites attribute, which we haven't talked about. Also, Course c2 isn't depicted in Figure 4-2 because, strictly speaking, c2 isn't engaged in this "discussion" between objects c and s; c2 is being talked about, but isn't doing any talking itself!)

  3. Student object s replies with the value true to c, indicating that s has successfully completed the prerequisite course. (We will for the time being ignore the details as to how s determines this; it involves interacting with s's internal transcript attribute, which we haven't fully explained the structure of just yet.)

  4. Convinced that the student has complied with the prerequisite requirements for the course, Course object c finishes the job of registering the student (internal details omitted for now) and confirms the registration by responding with a value of true to the originator of the service request.

This example was overly simplistic; in reality, Course c may have had to speak to numerous other objects:

  • A Classroom object (the room in which the course is to be held, to make sure that it has sufficient room for another student)

  • A DegreeProgram object (the degree sought by the student, to make sure that the requested course is indeed required for the degree that the student is pursuing)

and so forth—before sending a true response to indicate that the request to register Student s had been fulfilled. We'll see a slightly more complicated version of this message exchange later in the chapter.

Accessing Attributes via Dot Notation

Just as we use dot notation to formulate messages to be passed to objects, we can also use dot notation to refer to an object's attributes. For example, if we declare a reference variable x to be of type Student, we can refer to any of Student x's attributes via the following notation:

x.attribute_name

where the dot is used to qualify the name of the attribute of interest with the name of the reference variable representing the object of interest: x.name, x.gpa, and so forth.

Here are a few additional examples:

  // Instantiate three objects.
  Student x = new Student();
  Student y = new Student();
  Professor z = new Professor();
  // We may use dot notation to access attributes as variables.

  // Set student x's name ...
  x.name = "John Smith";

  // ... and student y's name.
  y.name = "Joe Blow";

  // Set professor z's name to be the same as student x's name.
  z.name = x.name;

  // Compute the total of the two students' ages.
  int i = x.age + y.age;

  // Set the professor's age to be 40.
  z.age = 40;

However, we'll see later in this chapter that just because we can access attributes this way doesn't mean that we should. There are many reasons why we'll want to restrict access to an object's data so as to give the object complete control over when and how its data is altered, and several mechanisms for how we can do so.

Delegation

If a request is made of an object A and, in fulfilling the request, A in turn requests assistance from another object B, this is known as delegation by A to B. The concept of delegation among objects is exactly the same as delegation between people in the real world: if your "significant other" asks you to mow the lawn while he or she is out running errands, and you in turn hire a neighborhood teenager to mow the lawn, then, as far as your partner is concerned, the lawn has been mowed. The fact that you delegated the activity to someone else is (hopefully!) irrelevant.

The fact that delegation has occurred between objects is often transparent to the initiator of a message, as well. In our previous message passing example, Course c delegated part of the work of registering Student s back to s when c asked s to verify a prerequisite course. However, from the perspective of the originator of the registration request—c.Register(s);—this seems like a simple interaction: namely, the requestor asked c to register a student, and it did so! All of the "behind the scenes" details of what c had to do to accomplish this are hidden from the requestor (see Figure 4-3).

Click To expand
Figure 4-3: A requestor sees only the external details of a message exchange.

Access to Objects

The only way that an object A can pass a message to an object B is if A has access to a handle on B. This can happen in several different ways:

  • Object A might maintain a handle/reference to B as one of A's attributes; for example, here's the example from Chapter 3 of a Student object having a Professor reference as an attribute:

        public class Student
        {
          // Attributes.
          string name;
          Professor facultyAdvisor;
          // etc.
    
  • Object A may be handed a reference to B as an argument of one of A's methods. This is how Course object c obtained access to Student object s in the preceding message passing example, when c's Register method was called:

        c.Register(s);
    
  • A reference to object B may be made "globally available" to the entire application, such that all other objects can access it.We'll discuss techniques for doing so when we construct the SRS in Part Three of the book.

  • Object A may have to explicitly request a handle/reference to B by calling a method on some third object C. Since this is potentially the most complex way for A to obtain a handle on B, we'll illustrate this with an example.

Going back to the example interaction between Course object c and Student object s from a few pages ago, let's complicate the interaction a bit.

  • First, we'll introduce a third object: a Transcript object t, which represents a record of all courses taken by Student object s.

  • Furthermore, we'll assume that Student s maintains a handle on Transcript t as one of s's attributes (specifically, the transcript attribute), and conversely, that Transcript t maintains a handle on its "owner", Student s, as one of t's attributes (see Figure 4-4).

Click To expand
Figure 4-4: A more complex message passing example involving three objects
  1. In this enhanced object interaction, the first step is exactly as previously described: namely, a Course object c receives the message

        c.Register(s);
    

    where s represents a Student object.

  2. Now, instead of Course c sending the message s.SuccessfullyCompleted(c2) to Student s as before, where c2 represents a prerequisite Course, Course object c instead sends the message

    s.GetTranscript();
    

    to the Student, because c wants to check s's transcript firsthand. This message corresponds to a method on the Student class whose header is defined as follows:

    Transcript GetTranscript()
    

    Note that this method is defined to return a Transcript object reference: specifically, a handle on the Transcript object t belonging to this student.

  3. Because Student s maintains a handle on its Transcript object as an attribute, it's a snap for s to respond to this message by passing a handle on t back to Course object c.

  4. Now that Course c has its own temporary handle on Transcript t, object c can talk directly to t. Object c proceeds to ask t whether t has any record of c's prerequisite course c2 having successfully been completed by Student s by passing the message

    t.SuccessfulCompletion(c2);
    

    This implies that there is a method defined for the Transcript class with the header

    bool SuccessfulCompletion(Course c)
    
  5. Transcript object t answers back with a response of true to Course c, indicating that Student s has indeed successfully completed the prerequisite course in question. (Note that Student s is unaware that c is talking to t; object s knows that it was asked by c to return a handle to t in an earlier message, but s has no insights as to why c asked for the handle.)

    Note?/td>

    This is not unlike the real-world situation in which person A asks person B for person C's phone number, without telling B why he or she wants to call C.

  6. Satisfied that Student s has complied with its prerequisite requirements, Course object c finishes the job of registering the student (internal details omitted for now) and confirms the registration by responding with a value of true to the originator of the registration request that first arose in step 1. Now that c has finished with this transaction, it discards its (temporary) handle on t.

Note that, from the perspective of whoever sent the original message

        c.Register(s);

to Course c, this more complicated interaction appears identical to the earlier, simpler interaction, as shown in Figure 4-5.

Click To expand
Figure 4-5: The external details of this more complex interaction appear identical from the requestor's standpoint.

All the sender of the original message knows is that Course c eventually responded with a value of true to the request.

Objects As Clients and Suppliers

In the preceding example of message passing between a Course object and a Student object, we can consider Course object c to be a client of Student object s, because c is requesting (by initiating a message) that s perform one of its methods—namely, GetTranscript—as a service to c. This is identical to the real-world concept of you, as a client, requesting the services of an accountant, or an attorney, or an architect. Similarly, c is a client of Transcript t when c asks t to perform its SuccessfulCompletion method. We therefore refer to code that invokes a method on an object A as client code relative to A because the code benefits from the service(s) performed by A.

Let's look at a few examples of client code. The following code corresponds to the message passing example involving a Course, Student, and Transcript object from a few pages back, and as promised earlier, provides the context for the messages that were passed in that example.

The following code snippet, taken from the Main method of an application, instantiates two objects and invokes a method on one of them, which gets them "talking":


    static void Main() {
      Course c = new Course();
      Student s = new Student();

      // Details omitted.

      // Invoke a method on Course object c.
      // (This is labeled as message (1) in the earlier figure; the returned
      // value, labeled as (6) in that figure, is being "ignored" in this
      // case.)
      c.Register(s);

      // etc.
    }

In this example, the Main method is considered to be client code relative to Course object c because it calls upon c to perform its Register method as a service.

Let's now look at the code that implements the body of the Register method, inside of the Course class:

    public class Course
    {
      // details omitted ...

    public bool Register(Student s) {
      boolean outcome = false;

      // Request a handle on Student s's Transcript object.
       // (This is labeled as message (2) in the earlier figure.)
       Transcript t = s.GetTranscript();
       // (The return value from this method is labeled as (3) in
       // the earlier figure.)

       // Now, request a service on that Transcript object.
       // (Assume that c2 is a handle on some prerequisite Course ...)
       // (This is labeled as message (4) in the earlier figure.)
       if (t.SuccessfulCompletion(c2)) {
         // (This next return value is labeled as (5) in the earlier figure.)
         outcome = true;
       }
       else {
         outcome = false;
       }
         return outcome;
       }

       // etc.
     }

We see that the Register method body is considered to be client code relative to both Student object s and Transcript object t because this code calls upon s and t to each perform a service.

Whenever an object A is a client of object B, object B in turn can be thought of as a supplier of A.

Note that the roles of client and supplier are not absolute between two objects; such roles are only relevant for the duration of a particular message passing event. If I ask you to pass me the bread, I am your client, and you are my supplier; and if a moment later you ask me to pass you the butter, then you are my client, and I am your supplier.

Note?/td>

The notion of clients and suppliers is discussed further in Object-Oriented Software Construction by Bertrand Meyer (Prentice Hall).


Team LiB
Previous Section Next Section