Previous Section  < Day Day Up >  Next Section

3.5. Methods

Methods are to classes as verbs are to sentences. They perform the actions that define the behavior of the class. A method is identified by its signature, which consists of the method name and the number and data type of each parameter. A signature is considered unique as long as no other method has the same name and matching parameter list. In addition to parameters, a method has a return type?tt>void if nothing is returned梐nd a modifier list that determines its accessibility and polymorphic behavior.

For those who haven't recently boned up on Greek or object-oriented principles, polymorphism comes from the Greek poly (many) and morphos (shape). In programming terms, it refers to classes that share the same methods but implement them differently. Consider the ToString method implemented by all types. When used with an int type, it displays a numeric value as text; yet on a class instance, it displays the name of the underlying class梐lthough this default should be overridden by a more meaningful implementation.

One of the challenges in using .NET methods is to understand the role that method modifiers play in defining the polymorphic behavior of an application. A base class uses them to signal that a method may be overridden or that an inheriting class must implement it. The inheriting class, in turn, uses modifiers to indicate whether it is overriding or hiding an inherited method. Let's look at how all this fits together.

Method Modifiers

In addition to the access modifiers, methods have seven additional modifiers shown in Table 3-4. Five of these?tt>new, virtual, override, sealed, and abstract梡rovide a means for supporting polymorphism.

Table 3-4. Method Modifiers

Modifier

Description

static

The method is part of the class's state rather than any instances of the class. This means that it can be referenced directly by specifying classname.method (parameters) without creating an instance of the class.

virtual

Designates that the method can be overridden in a subclass. This cannot be used with static or private access modifiers.

override

Specifies that the method overrides a method of the same name in a base class. This enables the method to define behavior unique to the subclass. The overriden method in the base class must be virtual.

new

Permits a method in an inherited class to "hide" a non-virtual method with a same name in the base class. It replaces the original method rather than overriding it.

sealed

Prevents a derived class from overriding this method.

  • Is used in a derived class that will serve as the base for its own subclasses.

  • Must be used with the override modifier.

abstract

The method contains no implementation details and must be implemented by any subclass. Can only be used as a member of an abstract class.

extern

Indicates that the method is implemented externally. It is generally used with the DLLImport attribute that specifies a DLL to provide the implementation.


Static Modifier

As with other class members, the static modifier defines a member whose behavior is global to the class and not specific to an instance of a class. The modifier is most commonly used with constructors (described in the next section) and methods in helper classes that can be used without instantiation.

Listing 3-5. Static Method

using System;

class Conversions

{

   // class contains functions to provide metric conversions

   private static double cmPerInch = 2.54;

   private static double gmPerPound = 455;

   public static double inchesToMetric(double inches) {

      return(inches * cmPerInch);

   }

   public static double poundsToGrams(double pounds) {

      return(pounds * gmPerPound);

   }

}

class Test

{

   static void Main() {

      double cm, grams;

      cm = Conversions.inchesToMetric(28.5);

      grams = Conversions.poundsToGrams(984.4);

   }

}


In this example, the Conversions class contains methods that convert units from the English to metric system. There is no real reason to create an instance of the class, because the methods are invariant (the formulas never change) and can be conveniently accessed using the syntax classname.method(parameter).

Method Inheritance with Virtual and Override Modifiers

Inheritance enables a program to create a new class that takes the form and functionality of an existing (base) class. The new class then adds code to distinguish its behavior from that of its base class. The capability of the subclass and base class to respond differently to the same message is classical polymorphism. In practical terms, this most often means that a base and derived class(es) provide different code for methods having the same signature.

By default, methods in the base class cannot be changed in the derived class. To overcome this, .NET provides the virtual modifier as a cue to the compiler that a method can be redefined in any class that inherits it. Similarly, the compiler requires that any derived class that alters a virtual method preface the method with the override modifier. Figure 3-2 and Listing 3-6 provide a simple illustration of this.

Listing 3-6. Virtual Methods

using System;

class Fiber

{

   public  virtual string ShowMe() { return("Base");}

}

class Natural:Fiber

{

   public override string ShowMe() { return("Natural");}

}

class Cotton:Natural

{

   public override string ShowMe() { return("Cotton");}

}

class Test

{

   static void Main ()

   {

      Fiber fib1 = new Natural();  // Instance of Natural

      Fiber fib2 = new Cotton();   // Instance of Cotton

      string fibVal;

      fibVal = fib1.ShowMe();      // Returns "Natural"

      fibVal = fib2.ShowMe();      // Returns "Cotton"

   }

}


Figure 3-2. Relationship between base class and subclasses for Listing 3-6


In this example, Cotton is a subclass of Natural, which is itself a subclass of Fiber. Each subclass implements its own overriding code for the virtual method ShowMe.


fib1.ShowMe();    //returns  "Natural"

fib2.ShowMe();    //returns  "Cotton"


A subclass can inherit a virtual method without overriding it. If the Cotton class does not override ShowMe(), it uses the method defined in its base class Natural. In that case, the call to fib2.ShowMe() would return "Natural".

New Modifier and Versioning

There are situations where it is useful to hide inherited members of a base class. For example, your class may contain a method with the same signature as one in its base class. To notify the compiler that your subclass is creating its own version of the method, it should use the new modifier in the declaration.

ShowMe() is no longer virtual and cannot be overridden. For Natural to create its own version, it must use the new modifier in the method declaration to hide the inherited version. Observe the following:

  • An instance of the Natural class that calls ShowMe()invokes the new method:

    
    Natural myFiber = new Natural();
    
    string fibTtype = myFiber.ShowMe();  // returns "Natural"
    
    

  • Cotton inherits the new method from Natural:

    
    Cotton myFiber = new Cotton();
    
    string fibType = myFiber.ShowMe();   // returns "Natural"
    
    

  • If ShowMe were declared as private rather than public in Natural, Cotton would inherit ShowMe from Fiber, because it cannot inherit a method that is out of scope.

Listing 3-7. Using the New Modifier for Versioning

public class Fiber

{

   public string ShowMe()        {return("Base");}

   public virtual string GetID() {return("BaseID");}

}

public class Natural:Fiber

{

   // Hide Inherited version of ShowMe

   new public string ShowMe()     {return("Natural");}

   public override string GetID() {return("NaturalID");}

   }

public class Cotton:Natural

{  // Inherits two methods: ShowMe() and GetID()

}


Sealed and Abstract Modifiers

A sealed modifier indicates that a method cannot be overridden in an inheriting class; an abstract modifier requires that the inheriting class implement it. In the latter case, the base class provides the method declaration but no implementation.

This code sample illustrates how an inheriting class uses the sealed modifier to prevent a method from being overridden further down the inheritance chain. Note that sealed is always paired with the override modifier.


class A {

   public virtual void PrintID{....}

}

class B: A {

   sealed override public void PrintID{...}

}

class C:B {

   // This is illegal because it is sealed in B.

   override public void PrintID{...}

}


An abstract method represents a function with a signature梑ut no implementation code梩hat must be defined by any non-abstract class inheriting it. This differs from a virtual method, which has implementation code, but may be redefined by an inheriting class. The following rules govern the use abstract methods:

  • Abstract methods can only be declared in abstract classes; however, abstract classes may have non-abstract methods.

  • The method body consists of a semicolon:

    
    public abstract void myMethod();
    
    

  • Although implicitly virtual, abstract methods cannot have the virtual modifier.

  • A virtual method can be overridden by an abstract method.

When facing the decision of whether to create an abstract class, a developer should also consider using an interface. The two are similar in that both create a blueprint for methods and properties without providing the implementation details. There are differences between the two, and a developer needs to be aware of these in order to make the better choice. The section on interfaces in this chapter offers a critical comparison.

Passing Parameters

By default, method parameters are passed by value, which means that a copy of the parameter's data梤ather than the actual data梚s passed to the method. Consequently, any change the target method makes to these copies does not affect the original parameters in the calling routine. If the parameter is a reference type, such as an instance of a class, a reference to the object is passed. This enables a called method to change or set a parameter value.

C# provides two modifiers that signify a parameter is being passed by reference: out and ref. Both of these keywords cause the address of the parameter to be passed to the target method. The one you use depends on whether the parameter is initialized by the calling or called method. Use ref when the calling method initializes the parameter value, and out when the called method assigns the initial value. By requiring these keywords, C# improves code readability by forcing the programmer to explicitly identify whether the called method is to modify or initialize the parameter.

The code in Listing 3-8 demonstrates the use of these modifiers.

Listing 3-8. Using the ref and out Parameter Modifiers

class TestParms

{

   public static void FillArray(out double[] prices)

   {

      prices = new double[4] {50.00,80.00,120.00,200.00};

   }

   public static void UpdateArray(ref double[] prices)

   {

      prices[0] = prices[0] * 1.50;

      prices[1] = prices[1] * 2.0;

   }

   public static double TaxVal(double ourPrice,

                               out double taxAmt)

   {

      double totVal = 1.10 * ourPrice;

      taxAmt = totVal ?ourPrice;

      ourPrice = 0.0;  // Does not affect calling parameter

      return totVal;

   }

}

class MyApp

{

   public static void Main()

   {

      double[] priceArray;

      double taxAmt;

      //   (1) Call method to initialize array

      TestParms.FillArray(out priceArray);

      Console.WriteLine( priceArray[1].ToString());  // 80

      //   (2) Call method to update array

      TestParms.UpdateArray(ref priceArray);

      Console.WriteLine( priceArray[1].ToString());  // 160

      //   (3) Call method to calculate amount of tax.

      double ourPrice = 150.00;

      double newtax = TestParms.TaxVal(ourPrice, out taxAmt);

      Console.WriteLine( taxAmt.ToString());  // 15

      Console.WriteLine( ourPrice);           // 150.00

   }

}


In this example, the class MyApp is used to invoke three methods in the TestParms class:

  1. FillArray is invoked to initialize the array. This requires passing the array as a parameter with the out modifier.

  2. The returned array is now passed to a second method that modifies two elements in the array. The ref modifier indicates the array can be modified.

  3. ourPrice is passed to a method that calculates the amount of tax and assigns it to the parameter taxAmt. Although ourPrice is set to 0 within the method, its value remains unchanged in the calling method because it is passed by value.

C# includes one other parameter modifier, params, which is used to pass a variable number of arguments to a method. Basically, the compiler maps the variable number of arguments in the method invocation into a single parameter in the target method. To illustrate, let's consider a method that calculates the average for a list of numbers passed to it:


// Calculate average of variable number of arguments

public static double GetAvg(params double[] list)

{

   double tot = 0.0;

   for (int i = 0 ; i < list.Length; i++)

      tot += list[i];

   return tot / list.Length;

}


Except for the params modifier, the code for this method is no different than that used to receive an array. Rather than sending an array, however, the invoking code passes an actual list of arguments:


double avg;

avg = TestParms.GetAvg(12,15, 22, 5, 7 ,19);

avg = TestParms.GetAvg(100.50, 200, 300, 55,88,99,45);


When the compiler sees these method calls, it emits code that creates an array, populates it with the arguments, and passes it to the method. The params modifier is essentially a syntactical shortcut to avoid the explicit process of setting up and passing an array.

Core Note

The params keyword can only be used with the last parameter to a method.


    Previous Section  < Day Day Up >  Next Section