Core C# and .NET
< Day Day Up > |
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.
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:
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:
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 } } In this example, the class MyApp is used to invoke three methods in the TestParms class:
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
|
< Day Day Up > |