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—void if nothing is returned—and
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—although 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—new, virtual, override,
sealed, and abstract—provide a means for supporting
polymorphism.
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.
|
Prevents a derived class from overriding this method.
| |
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"
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—but no implementation code—that 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—rather than the actual data—is 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 } }
-
FillArray is invoked to initialize the array. This requires passing the array as a parameter with the out modifier.
-
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.
-
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.
No comments:
Post a Comment
Comment Here