Arrays and Methods

   


Array elements, such as accountBalances[0], as well as array object references, such as accountBalances, can be arguments in a method call, as well as return values from a method. The next few sections show you how and discuss the implications and possibilities.

Array Elements As Method Arguments

Recall that an array element, like accountBalances[0] of base type decimal, can be applied in any position of the source code where a simple variable of type decimal can be used. This also allows for array elements to be used as method arguments in method calls.

For example, the following line is a method header of the MaxAmount method specifying that method calls must provide two arguments both of type decimal.

 private static decimal MaxAmount(decimal a, decimal b) 

Because the two following arrays are both of base type decimal

 decimal [] person1Sales = { 40000, 10000, 25000, 50000, 33000, 60000} ; decimal [] person2Sales = { 80000, 3000, 110000, 40000, 33000, 59000} ; 

their array elements can be used as arguments in a method call to MaxAmount as follows

 ...MaxArray(person1Sales[0], person2Sales[0]... 

Listing 10.6 completes this example and represents a fully working program. The sales figures of two sales people are kept in the two arrays person1Sales and person2Sales (see lines 7 and 8) for the first six months of the current year. The company wants to see the maximum individual sales for each month. This is achieved by comparing corresponding pairs of array elements from the two arrays in the MaxAmount method (lines 18 24) that return the greater of the two values passed to it as arguments.

Listing 10.6 MaxSales.cs
01: using System; 02: 03: class MaxSales 04: { 05:     public static void Main() 06:     { 07:         decimal [] person1Sales = { 40000, 10000, 25000, 50000, 33000, 60000} ; 08:         decimal [] person2Sales = { 80000, 3000, 110000, 40000, 33000, 59000} ; 09: 10:         Console.WriteLine("Max individual sales for each of the first six months: "); 11:         for (int i = 0; i < 6; i++) 12:         { 13:             Console.WriteLine("Month { 0} : { 1,11:C} ", 14:                 i+1, MaxAmount(person1Sales[i], person2Sales[i])); 15:         } 16:     } 17: 18:     private static decimal MaxAmount(decimal a, decimal b) 19:     { 20:         if (a > b) 21:             return a; 22:         else 23:             return b; 24:     } 25: } Max individual sales for each of the first six months: Month 1:  $80,000.00 Month 2:  $10,000.00 Month 3: $110,000.00 Month 4:  $50,000.00 Month 5:  $33,000.00 Month 6:  $60,000.00 

The MaxAmount method (lines 18 24) has two formal parameters (a and b), both of type decimal. It returns the maximum value of a and b back to the caller.

The method call MaxAmount(person1Sales[i], person2Sales[i]) of line 14 resides inside a for loop that repeats itself six times, once for every month. During the first repetition, i is equal to 0. The two expressions person1Sales[i] and person2Sales[i] become person1Sales[0] and person2Sales[0], which again are evaluated to 40000 and 80000. The method call can now be perceived as MaxAmount(40000, 80000);, returning the value 80000 (see the sample output) because 80000 is greater than 40000. Observe that the indexed expressions person1Sales[i] and person2Sales[i] of line 14 are evaluated to determine which array element value to send off to the method. At the next repetition of the loop body, i holds the value 1 and the method call becomes MaxAmount(10000, 3000) returning the value 10000. This cycle of events are repeated six times.

Note

graphics/common.gif

There is no indication in the method header of MaxAmount (see line 18 of Listing 10.6) that this method accepts array elements as arguments. In fact, it is irrelevant from any method's point of view whether the arguments passed to it originates from a literal, constant, variable, or an array element, as long as the value passed along is of the specified type, in this case decimal.


Array References As Method Arguments

As shown in Chapter 4, "A Guided Tour through C#: Part II" it is possible to pass values of simple types as arguments to a method. It turns out that a reference to an array object can also constitute an argument.

To specify that a method accepts arguments of type array-object-reference, we need to include a suitable formal parameter in the method header. An example is given in the following line:

 public static int Sum (uint [] numbers) 

The declaration of the formal parameter is identical to the conventional declaration of an array variable; this time, the declaration is merely inserted between the parentheses containing the formal parameters of the method header. In this case, the method header declares numbers to be a formal parameter of type reference-to-array-object-of-base-type-uint.

Figure 10.9 shows the rest of the Sum method (lines 25 33) along with the events that take place when a method call passes a reference to this method with an argument.

Figure 10.9. Providing an array reference as argument with method call.
graphics/10fig09.gif

Line 1 declares populationSizes to be an array variable holding a reference to an array object of base type uint, and assigns it an array object of length 3. Lines 2 4 initializes the three array elements of populationSizes. When the method call Sum(populationSizes) (line 10) is executed, the reference held by populationSizes is passed on to numbers of the Sum method, which is then referencing an identical array object during its execution. As a result, it is able to calculate the sum of, in this case, the array elements of populationSizes through its access to the entire set of individual array elements (lines 27 31). The resulting sum is passed back by the return totalSum; statement (line 32).

Note

graphics/common.gif

An argument of an array variable provided for a method call does not include any square brackets, making the following line incorrect:

 Sum(populationSizes[])    //Incorrect. [] should not be included 


Listing 10.7 provides the complete source code of a program that utilizes C#'s ability to provide array object references as method call arguments. The method AverageAge (lines 27 35) calculates the average of any array object (of any length) of base type byte passed to it as a reference. The user is asked to enter two sets of ages one for a scout camp containing six scouts and one for the crewmembers of a space shuttle containing six astronauts. The averages of the two sets are calculated and printed onscreen in each instance.

Listing 10.7 AverageAgeCalculator
01: using System; 02: 03: class AverageAgeCalculator 04: { 05:     public static void Main() 06:     { 07:         byte[] agesScoutCamp = new byte[4]; 08:         byte[] agesSpaceShuttle = new byte [6]; 09: 10:         Console.WriteLine("Enter ages for { 0}  scouts: ", agesScoutCamp.Length); 11:         for (int i = 0; i < agesScoutCamp.Length; i++) 12:         { 13:             Console.Write("Enter age for scout number { 0} : ", i + 1); 14:             agesScoutCamp[i] = Convert.ToByte(Console.ReadLine()); 15:         } 16:         Console.WriteLine("Average age of scouts: " + AverageAge(agesScoutCamp)); 17: 18:         Console.WriteLine("\nEnter ages for { 0}  astronauts",  graphics/ccc.gifagesSpaceShuttle.Length); 19:         for (int i = 0; i < agesSpaceShuttle.Length; i++) 20:         { 21:             Console.Write("Enter age for astronaut number { 0} : ", i + 1); 22:             agesSpaceShuttle[i] = Convert.ToByte(Console.ReadLine()); 23:         } 24:         Console.WriteLine("Average age of astronauts: " +  graphics/ccc.gifAverageAge(agesSpaceShuttle)); 25:     } 26:  27:     public static byte AverageAge(byte[] ages) 28:     { 29:         int ageSum = 0; 30:          // The sum of all ages is assigned to ageSum 31:         for (int i = 0; i < ages.Length; i++) 32:             ageSum += ages[i]; 33:          // Calculate the average age and return it back to the caller 34:         return (byte)(ageSum / ages.Length); 35:     } 36: } Enter ages for 4 scouts: Enter age for scout number 1: 10<enter> Enter age for scout number 2: 9<enter> Enter age for scout number 3: 8<enter> Enter age for scout number 4: 9<enter> Average age of scouts: 9 Enter ages for 6 astronauts Enter age for astronaut number 1: 34<enter> Enter age for astronaut number 2: 38<enter> Enter age for astronaut number 3: 30<enter> Enter age for astronaut number 4: 35<enter> Enter age for astronaut number 5: 33<enter> Enter age for astronaut number 6: 34<enter> Average age of astronauts: 34 

Lines 7 and 8 declare and define the two arrays holding the ages of two groups of people 4 scouts in a scout camp and 6 crew members of a space shuttle.

Lines 10 15 reads in the ages for the children of the scout camp from the user and assigns each age to an array element of the agesScoutCamp variable. An identical procedure is utilized in lines 18 23, this time for the space shuttle crew. Line 16 utilizes the functionality of the AverageAge method to calculate and print the average age of the scouts contained in the array agesScoutCamp. The array object to be analyzed by the method is provided by sending it the agesScoutCamp reference as an argument. It is received by the formal parameter ages of type byte[] in line 27 and used for the necessary average calculations in the AverageAge method of lines 27 35. A similar set of events is used in line 24 to calculate and print the average age of the space shuttle crew members.

A reference contained in an array variable takes up just 4 bytes of memory. This is in stark contrast to the size of the object itself. For example, an array object containing 100 decimal values occupies (100 x 16) 1600 bytes. Thus, when a reference is passed from one array variable to another, or from method argument to formal parameter, only 4 bytes are transferred in contrast to the 1600 bytes given here as an example. Not only does this save memory, it also speeds up the execution of a program.

Cloning an Array Object

Passing array object references to other parts of the program, whether this be as method arguments or simply to other array variables, can be a very efficient way of passing around data in a program as mentioned in the previous section, but it poses certain risks as explained in the following text.

1) Methods that accidentally change your array.

When a method has been passed a reference to an array object, it cannot only access, but also can change the value of each array element. The change might be deliberate or accidental. An example of the latter is provided here. Suppose that Listing 10.7 contained a flaw in line 32, as shown in the two code lines following this paragraph, causing every array element to be incremented by 10, instead of leaving it unchanged as the original correct line 32 did. All array elements contained in any array passed to this method would then have their values incremented by 10, accidentally and possibly without the programmer's awareness.

 31:   for (int i = 0; i < ages.Length; i++) 32:   ages[i] += 10;  //ages[i] is accidentally incremented by 10 

Tip

graphics/bulb.gif

When passing your array references around to other methods, perhaps written by other more or less trusted programmers, you need to take extra care in ensuring that only deliberate changes are being made to your arrays during those calls.


2) More than one array variable referencing the same array object can lead to overly complicated code.

To have several array variables reference the same array object can result in overly complicated code and difficult to trace bugs, because seemingly separate variables can alter each other's values in different parts of a program. This problem has already been hinted at earlier in this chapter.

It is possible to use System.Array's built in method Clone() to make a full copy of an array object and thereby avoid the problems previously mentioned. To illustrate the Clone() methods effects and how it is applied, we first declare our familiar accountBalances array referencing an array object with four array elements by writing the following:

 decimal [] accountBalances = { 3000, 4000, 1000, 2000} ; 

We then declare another array variable called balancesClone of the same base type:

 decimal [] balancesClone; 

The situation after this last statement has been executed, is depicted in Figure 10.10. accountBalances references an object of four array elements, and balanceClone is referencing null because it has not yet been assigned any array object.

Figure 10.10. Declaring two array variables and creating one array object.
graphics/10fig10.gif

The next line (shown after this paragraph) calls the Clone() method by applying the dot operator to accountBalances followed by Clone(). The result is the creation of a new array object identical to, but separate from, the array object referenced by accountBalances; its reference is assigned to balancesClone.

 balancesClone = (decimal []) accountBalances.Clone(); 

First, notice that the Clone() method is called in the usual manner by writing the name of the array variable (in this case, accountBalances) followed by the dot operator and finally the name of the method, which is Clone() in this case.

Second, observe that we had to apply the cast operator (decimal []) in front of the method call. To fully understand why, you need to know more aspects of OOP. For now, I will merely give you this brief analogous explanation the method call accountBalances.Clone() returns an anonymous container enclosing a value of type decimal []. To unwrap this value so it can be assigned to balancesClone, we need to apply the (decimal []) cast operator.

The process involved in executing the previous statement and its final result is illustrated in Figure 10.11.

Figure 10.11. The effects of calling the Clone() method.
graphics/10fig11.gif

Because accountBalances and balancesClone are referencing separate array objects, accountBalances[0] and balancesClone[0] refer to different array elements. As a result, we can write the following

 balancesClone[0] = 20000; 

without affecting the value in accountBalances[0].

Listing 10.8 contains a source program demonstrating how the Clone() method is applied and highlights the difference between this approach of copying an array and the familiar procedure of assigning an array object reference with the assignment operator = . Overall, the program contains five initial speeds (could be car speeds in a racing game) in an array. Each initial speed must be altered by multiplying it by an acceleration factor. We want to keep track of the original speeds as well as the current speeds, so the original speeds are copied to a new array of speeds that are subject to the changes. The question is which copy mechanism should we use the Clone() method (as in line 12), or simply the assignment operator (line 21)?

Listing 10.8 ArrayCloning.cs
01: using System; 02: 03: class ArrayCloning 04: { 05:     public static void Main() 06:     { 07:         const float accellerationFactor = 1.1f; 08:         float [] initialSpeeds  = { 50, 100, 35, 60, 20} ; 09:         float [] currentSpeeds; 10: 11:         Console.WriteLine("Assigning a clone:"); 12:         currentSpeeds = (float[])initialSpeeds.Clone(); 13:         for (int i = 0; i < currentSpeeds.Length; i++) 14:         { 15:             currentSpeeds[i] = currentSpeeds[i] * accellerationFactor; 16:             Console.WriteLine("Current speed { 0,2} : { 1,4}    Initial speed { 2,2} : graphics/ccc.gif { 3,4} ", 17:                 i+1, currentSpeeds[i], i+1, initialSpeeds[i]); 18:         } 19: 20:         Console.WriteLine("\nAssigning a reference:"); 21:         currentSpeeds = initialSpeeds; 22:         for (int i = 0; i < currentSpeeds.Length; i++) 23:         { 24:             currentSpeeds[i] = currentSpeeds[i] * accellerationFactor; 25:             Console.WriteLine("Current speed { 0,2} : { 1,4}    Initial speed { 2,2} : graphics/ccc.gif { 3,4} ", 26:                 i+1, currentSpeeds[i], i+1, initialSpeeds[i]); 27:         } 28:     } 29: } Assigning a clone: Current speed  1:   55   Initial speed  1:   50 Current speed  2:  110   Initial speed  2:  100 Current speed  3: 38.5   Initial speed  3:   35 Current speed  4:   66   Initial speed  4:   60 Current speed  5:   22   Initial speed  5:   20 Assigning a reference: Current speed  1:   55   Initial speed  1:   55 Current speed  2:  110   Initial speed  2:  110 Current speed  3: 38.5   Initial speed  3: 38.5 Current speed  4:   66   Initial speed  4:   66 Current speed  5:   22   Initial speed  5:   22 

Line 8 declares an array called initialSpeeds of base type float. It is initialized to contain 5 different speeds. The entire collection of speeds is to be changed by multiplying them by an acceleration factor. The program must keep track of the current speed resulting from the acceleration but, at the same time, cannot "forget" about the speeds initially stored in initialSpeeds. Consequently, we need two separate speed array objects one containing the initial speeds and the other containing the current speeds. currentSpeeds declared in line 9 is used for the latter purpose. Line 12 uses the Clone() method to create a new identical but separate array object with the same values as those referenced by initialSpeeds. The reference to this new object is assigned to currentSpeeds. Line 15 multiplies each array element of currentSpeeds with the accellerationFactor constant (declared in line 7), and the content of both currentSpeeds and initialSpeeds are printed in lines 16 and 17. As you can see from the sample output, currentSpeeds have changed, whereas initialSpeeds correctly has been left untouched.

The next part of the program (from line 21) begins by assigning the reference contained inside initialSpeeds to currentSpeeds, but without creating an accompanying new array object, so the two array variables are referencing the same array object. The problem with this approach is exposed through lines 22 27. Line 24 once again applies the acceleration factor to the array elements of currentSpeeds, but this time the array elements of initialSpeeds are affected, which is seen in the latter half of the sample output created by lines 25 and 26.

In the previous example, each array element holds values of type int, which is a value type. As mentioned earlier, arrays can also be declared to let its array elements hold values of a reference type. In this case, each array element will reference an object. Two important terms are used in connection with copying array objects of this kind shallow copy and deep copy.

When an array object of a reference base type is shallow copied, the objects referenced by the array variables are not copied. Only the references themselves are copied to the new array elements. Thus, each array element of the new array object will reference the same object as the corresponding array element in the original array object.

The array elements of the deep copy will, like the shallow copy, contain copies of the references found in the original array object. However, in contrast to the shallow copy, the referenced objects will also be new copies. The objects referenced by the array elements of the deep array copy will be separate from the objects referenced by the original array.

Methods specialized in copying array objects provide either a shallow or deep copy.

For example, the Clone() method generates a shallow copy of the array it is called through.


   


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net