13.11. More on Methods

We've discussed methods in a fair amount of detail in this chapter and in Part One of this book. Now it's time to round out the discussion with a few more important observations about methods.

13.11.1. Message Chaining

In OOPLs such as C#, it's quite commonplace to form complex expressions by concatenating one method call onto another via dot notation, a mechanism known as message chaining. Here's one hypothetical example:

Student s = new Student();
s.Name = "Fred";

Professor p = new Professor();
p.Name = "John";

Course c = new Course();
c.Name = "Math";

s.setFacultyAdvisor(p);
p.setCourseTaught(c);

Course c2 = new Course();

// A message "chain".
						c2.Name = "Beginning " + (s.GetFacultyAdvisor().GetCourseTaught().Name);

As you saw in Chapter 1, we evaluate expressions from innermost to outermost parentheses, left to right, so let's evaluate the expression in the last line of this snippet:

  1. Looking for the deepest level of nested parentheses, we see that part of the expression is two sets of parentheses deep, so we evaluate the leftmost deepest subexpression first—where the GetFacultyAdviser method is called on the Student reference s, which returns a reference to Professor p:

    s.GetFacultyAdvisor()

  2. Next, the GetCourseTaught() method is called on the Professor reference p, which returns a reference to Course c:

    p.GetCourseTaught()

  3. Next, we access the Name property of the Course object, which returns the string value "Math":

    c.Name

  4. We've now completed evaluating the expression enclosed within the innermost set of parentheses, effectively giving us this equivalent expression:

    c2.Name = "Beginning " + "Math";

So, we see in the final analysis that the outcome of the complex expression is to assign the name "Beginning Math" to the Name property of the Course object c2.

13.11.2. Method Hiding

In Chapter 5, you learned that a derived class can override a method that has been declared as virtual in a base class: the derived class declares a new version of the base class method with the same signature and includes the override keyword in the method declaration.

There is also a second way to replace the logic of a base class method, even one that hasn't been declared virtual in the base class, via a technique known as method hiding.

To "hide" a base class method, the derived class must define a method with the same signature using the new keyword in the method declaration in lieu of the override keyword. Here is a simple example in which the derived Student class hides the PrintDescription method first declared by the Person base class:

public class Person
{
  // details omitted

  // Note:  no virtual keyword -- no provision for overriding was
						// made by the designers of the Person class.
						public void PrintDescription() {
    // details omitted
  }
}

public class Student : Person
{
						// The Student class HIDES the PrintDescription()
						// method from the Person class via the use of the
						// new keyword.
						public new void PrintDescription() {
    // details omitted
  }
}

Any base class method—whether virtual or not—can be hidden; thus, we have a work-around (of sorts) if we find ourselves in a position of wanting to modify the behavior of a method derived from a base class that wasn't "prepped" for overriding by the original designer of the base class via the inclusion of the virtual keyword in the base class's method signature. Note, however, that hiding a nonvirtual base class method differs from truly overriding a virtual base class method in one very significant way, having to do with the notion of polymorphism that we discussed in Chapter 7. Let's explore this issue.

13.11.2.1. Method Hiding and Polymorphism

We learned in Chapter 7 that polymorphism refers to the ability of different objects belonging to different classes to respond to the same method call in different, class-specific ways. For example, assuming that ChooseMajor is a virtual method in the Student class that has been overridden by both the GraduateStudent- and UndergraduateStudent-derived classes, the version of ChooseMajor invoked on s in the following code will depend on what type of Student s is:

// Iterating through a List of Students.
List<Student> students = new List<Student>;

//  Other List details omitted.

foreach( Student s in students ) {

  // This next line of code is said to be polymorphic:
							// If s refers to a GraduateStudent at run time, the
							// GraduateStudent version of the ChooseMajor method will be
							// executed; and, if s refers to an UndergraduateStudent at run
							// time, the UndergraduateStudent version of ChooseMajor will be
							// executed.
							s.ChooseMajor();
}

By contrast, the version of a hidden method that is run for a given object is "locked in" at compile time. Let's now assume that ChooseMajor is a nonvirtual method in the Student class, and is hidden (not overridden) by both the GraduateStudent- and UndergraduateStudent-derived classes. While the following two invocations of ChooseMajor will indeed be of the derived classes' respective versions because we're invoking that method on reference variables explicitly declared to be a GraduateStudent and an UndergraduateStudent, respectively:

GraduateStudent g = new GraduateStudent();
g.ChooseMajor(); // GraduateStudent version of this method will execute

UndergraduateStudent s = new UndergraduateStudent();
s.ChooseMajor(); // UndergraduateStudent version of this method will execute

polymorphism will not be enabled in the next example—the Student base class's version of the ChooseMajor method will be invoked for both GraduateStudents and UndergraduateStudents because s is declared to be of type Student at compile time:

// Iterating through a List of Students.
foreach( Student s in students ) {

  // This next line of code is NOT polymorphic:
							// Regardless of whether s refers to a GraduateStudent at run
							// time or to an Undergraduate student, the Student version of
							// the ChooseMajor method will be executed.
							s.ChooseMajor();
}

We mentioned earlier that any method, virtual or not, can be hidden. Why might we want to hide a virtual base class method vs. overriding it if we lose the benefit of polymorphism? Because hidden methods yield better performance (they run faster) than virtual methods.

13.11.2.2. Final Notes Regarding Method Hiding

A few final points:

  • The original base class version of a hidden method can be called from within the "new" derived/hidden method using the base keyword, just as when overriding.

  • A derived class's "hidden" method can have a different return type than the base class version it is hiding.

  • An abstract method can't be hidden.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset