Understanding Parameter Arrays - Microsoft® Visual C#® 2012 Step by Step (2012)

Microsoft® Visual C#® 2012 Step by Step (2012)

Chapter 11. Understanding Parameter Arrays

After completing this chapter, you will be able to

§ Write a method that can accept any number of arguments by using the params keyword.

§ Write a method that can accept any number of arguments of any type by using the params keyword in combination with the object type.

§ Explain the differences between methods that take parameter arrays and methods that take optional parameters.

Parameter arrays are useful if you want to write methods that can take any number of arguments, possibly of different types, as parameters. If you are familiar with object-oriented concepts, you might be grinding your teeth in frustration at the previous sentence. After all, the object-oriented approach to solving this problem is to define overloaded methods. However, overloading is not always the most suitable approach, especially if you need to create a method that can take a truly variable number of parameters, each of which might vary in type whenever the method is invoked. This chapter describes how you can use parameter arrays to address situations such as this.

Overloading: A Recap

Overloading is the technical term for declaring two or more methods with the same name in the same scope. Being able to overload a method is very useful in cases where you want to perform the same action on arguments of different types. The classic example of overloading in Microsoft Visual C# is the Console.WriteLine method. This method is overloaded numerous times so that you can pass any primitive type argument. The following code example illustrates some of the ways in which the WriteLine method is defined in the Console class:

class Console

{

public static void WriteLine(Int32 value)

public static void WriteLine(Double value)

public static void WriteLine(Decimal value)

public static void WriteLine(Boolean value)

public static void WriteLine(String value)

...

}

NOTE

The documentation for the WriteLine method uses the structure types defined in the System namespace for its parameters rather than the C# aliases for these types. For example, the overload that prints out the value for an int actually takes an Int32 as the parameter. Refer back to Chapter 9, for a list of the structure types and their mappings to C# aliases for these types.

As useful as overloading is, it doesn’t cover every case. In particular, overloading doesn’t easily handle a situation in which the type of parameters doesn’t vary but the number of parameters does. For example, what if you want to write many values to the console? Do you have to provide versions of Console.WriteLine that can take two parameters of various combinations, other versions that can take three parameters, and so on? That would quickly get tedious. And wouldn’t the massive duplication of all these overloaded methods worry you? It should. Fortunately, there is a way to write a method that takes a variable number of arguments (a variadic method): you can use a parameter array (a parameter declared with the params keyword).

To understand how params arrays solve this problem, it helps to first understand the uses and shortcomings of ordinary arrays.

Using Array Arguments

Suppose you want to write a method to determine the minimum value in a set of values passed as parameters. One way is to use an array. For example, to find the smallest of several int values, you could write a static method named Min with a single parameter representing an array of intvalues:

class Util

{

public static int Min(int[] paramList)

{

// Verify that the caller has provided at least one parameter.

// If not, throw an ArgumentException exception – it is not possible

// to find the smallest value in an empty list.

if (paramList == null || paramList.Length == 0)

{

throw new ArgumentException("Util.Min: not enough arguments");

}

// Set the current minimum value found in the list of parameters to the first item

int currentMin = paramList[0];

// Iterate through the list of parameters, searching to see whether any of them

// are smaller than the value held in currentMin

foreach (int i in paramList)

{

// If the loop finds an item that is smaller than the value held in

// currentMin, then set currentMin to this value

if (i < currentMin)

{

currentMin = i;

}

}

// At the end of the loop, currentMin holds the value of the smallest

// item in the list of parameters, so return this value.

return currentMin;

}

}

NOTE

The ArgumentException class is specifically designed to be thrown by a method if the arguments supplied do not meet the requirements of the method.

To use the Min method to find the minimum of two int variables named first and second, you can write this:

int[] array = new int[2];

array[0] = first;

array[1] = second;

int min = Util.Min(array);

And to use the Min method to find the minimum of three int variables (named first, second, and third), you can write this:

int[] array = new int[3];

array[0] = first;

array[1] = second;

array[2] = third;

int min = Util.Min(array);

You can see that this solution avoids the need for a large number of overloads, but it does so at a price: you have to write additional code to populate the array that you pass in. You can, of course, use an anonymous array if you prefer, like this:

int min = Util.Min(new int[] {first, second, third});

However, the point is you still need to create and populate an array, and the syntax can get a little confusing. The solution is to get the compiler to write some of this code for you by using a params array as the parameter to the Min method.

Declaring a params Array

A params array enables you pass a variable number of arguments to a method. You indicate a params array by using the params keyword as an array parameter modifier when you define the method parameters. For example, here’s Min again—this time with its array parameter declared as aparams array:

class Util

{

public static int Min(params int[] paramList)

{

// code exactly as before

}

}

The effect of the params keyword on the Min method is that it allows you to call it by using any number of integer arguments without worrying about creating an array. For example, to find the minimum of two integer values, you can simply write this:

int min = Util.Min(first, second);

The compiler translates this call into code similar to this:

int[] array = new int[2];

array[0] = first;

array[1] = second;

int min = Util.Min(array);

To find the minimum of three integer values, you write the code shown here, which is also converted by the compiler to the corresponding code that uses an array:

int min = Util.Min(first, second, third);

Both calls to Min (one call with two arguments and another with three arguments) resolve to the same Min method with the params keyword. And as you can probably guess, you can call this Min method with any number of int arguments. The compiler just counts the number of intarguments, creates an int array of that size, fills the array with the arguments, and then calls the method by passing the single array parameter.

NOTE

C and C++ programmers might recognize params as a type-safe equivalent of the varargs macros from the header file stdarg.h. Java also has a varargs facility that operates in a similar manner to the params keyword in C#.

There are several points worth noting about params arrays:

§ You can’t use the params keyword with multidimensional arrays. The code in the following example will not compile:

§ // compile-time error

§ public static int Min(params int[,] table)

...

§ You can’t overload a method based solely on the params keyword. The params keyword does not form part of a method’s signature, as shown in this example:

§ // compile-time error: duplicate declaration

§ public static int Min(int[] paramList)

§ ...

§ public static int Min(params int[] paramList)

...

§ You’re not allowed to specify the ref or out modifier with params arrays, as shown in this example:

§ // compile-time errors

§ public static int Min(ref params int[] paramList)

§ ...

§ public static int Min(out params int[] paramList)

...

§ A params array must be the last parameter. (This means that you can have only one params array per method.) Consider this example:

§ // compile-time error

§ public static int Min(params int[] paramList, int i)

...

§ A non-params method always takes priority over a params method. This means that if you want to, you can still create an overloaded version of a method for the common cases. For example:

§ public static int Min(int leftHandSide, int rightHandSide)

§ ...

§ public static int Min(params int[] paramList)

...

The first version of the Min method is used when called using two int arguments. The second version is used if any other number of int arguments is supplied. This includes the case where the method is called with no arguments. Adding the non-params array method might be a useful optimization technique because the compiler won’t have to create and populate so many arrays.

Using params object[ ]

A parameter array of type int is very useful because it enables you to pass any number of int arguments in a method call. However, what if not only the number of arguments varies but also the argument type? C# has a way to solve this problem, too. The technique is based on the facts thatobject is the root of all classes and that the compiler can generate code that converts value types (things that aren’t classes) to objects by using boxing, as described in Chapter 8 You can use a parameters array of type object to declare a method that accepts any number of object arguments, allowing the arguments passed in to be of any type. Look at this example:

class Black

{

public static void Hole(params object [] paramList)

...

}

I’ve called this method Black.Hole, because no argument can escape from it:

§ You can pass the method no arguments at all, in which case the compiler will pass an object array whose length is 0:

§ Black.Hole();

// converted to Black.Hole(new object[0]);

§ You can call the Black.Hole method by passing null as the argument. An array is a reference type, so you’re allowed to initialize an array with null:

Black.Hole(null);

§ You can pass the Black.Hole method an actual array. In other words, you can manually create the array normally generated by the compiler:

§ object[] array = new object[2];

§ array[0] = "forty two";

§ array[1] = 42;

Black.Hole(array);

§ You can pass the Black.Hole method arguments of different types, and these arguments will automatically be wrapped inside an object array:

§ Black.Hole("forty two", 42);

//converted to Black.Hole(new object[]{"forty two", 42});

THE CONSOLE.WRITELINE METHOD

The Console class contains many overloads for the WriteLine method. One of these overloads looks like this:

public static void WriteLine(string format, params Object[] arg);

This overload enables the WriteLine method to support a format string argument that contains placeholders, each of which can be replaced at run time with a variable of any type. Here’s an example of a call to this method (the variables fname and lname are strings, mi is a char, and age is an int):

Console.WriteLine("Forename:{0}, Middle Initial:{1}, Last name:{2}, Age:{3}", fname,

mi, lname, age);

The compiler resolves this call into the following:

Console.WriteLine("Forename:{0}, Middle Initial:{1}, Last name:{2}, Age:{3}", new

object[4]{fname, mi, lname, age});

Using a params Array

In the following exercise, you will implement and test a static method named Sum. The purpose of this method is to calculate the sum of a variable number of int arguments passed to it, returning the result as an int. You will do this by writing Sum to take a params int[] parameter. You will implement two checks on the params parameter to ensure that the Sum method is completely robust. You will then call the Sum method with a variety of different arguments to test it.

Write a params array method

1. Start Microsoft Visual Studio 2012 if it is not already running.

2. Open the ParamsArray project, located in the \Microsoft Press\Visual CSharp Step By Step\Chapter 11\Windows X\ParamArrays folder in your Documents folder.

The ParamsArray project contains the Program class in the Programs.cs file, including the doWork method framework that you have seen in previous chapters. You will implement the Sum method as a static method of another class called Util (short for “utility”), which you will add to the project.

3. In Solution Explorer, right-click the ParamsArray project in the ParamsArray solution, point to Add, and then click Class.

4. In the Add New Item – ParamsArray dialog box, click the Class template in the middle pane, type Util.cs in the Name text box type, and then click Add.

The Util.cs file is created and added to the project. It contains an empty class named Util in the ParamsArray namespace.

5. Add a public static method named Sum to the Util class. This method should return an int and accept a params array of int values. It should look like this:

6. public static int Sum(params int[] paramList)

7. {

}

The first step in implementing the Sum method is to check the paramList parameter. Apart from containing a valid set of integers, it can also be null or it can be an array of zero length. In both of these cases, it is difficult to calculate the sum, so the best option is to throw anArgumentException exception. (You could argue that the sum of the integers in a zero-length array is 0, but treat this situation as an exception in this example.)

8. Add code shown below in bold to Sum. This code throws an ArgumentException exception if paramList is null.

The Sum method should now look like this:

public static int Sum(params int[] paramList)

{

if (paramList == null)

{

throw new ArgumentException("Util.Sum: null parameter list");

}

}

9. Add code to the Sum method that throws an ArgumentException exception if the length of the parameter list array is 0, as shown in bold here:

10.public static int Sum(params int[] paramList)

11.{

12. if (paramList == null)

13. {

14. throw new ArgumentException("Util.Sum: null parameter list");

15. }

16.

17. if (paramList.Length == 0)

18. {

19. throw new ArgumentException("Util.Sum: empty parameter list");

20. }

}

If the array passes these two tests, the next step is to add all the elements inside the array together. You can use a foreach statement to add all the elements together. You will need a local variable to hold the running total.

21.Declare an integer variable named sumTotal, and initialize it to 0 following the code from the preceding step.

22.public static int Sum(params int[] paramList)

23.{

24. ...

25. if (paramList.Length == 0)

26. {

27. throw new ArgumentException("Util.Sum: empty parameter list");

28. }

29.

30. int sumTotal = 0;

}

31.Add a foreach statement to the Sum method to iterate through the paramList array. The body of this foreach loop should add each element in the array to sumTotal. At the end of the method, return the value of sumTotal by using a return statement, as shown in bold here:

32.public static int Sum(params int[] paramList)

33.{

34. ...

35. int sumTotal = 0;

36. foreach (int i in paramList)

37. {

38. sumTotal += i;

39. }

40. return sumTotal;

}

41.On the BUILD menu, click Build Solution. Confirm that your solution builds without any errors.

Test the Util.Sum method

1. Display the Program.cs file in the Code and Text Editor window.

2. In the Code and Text Editor window, delete the // TODO: comment and add the following statement to the doWork method:

Console.WriteLine(Util.Sum(null));

3. On the DEBUG menu, click Start Without Debugging.

The program builds and runs, writing the following message to the console:

Exception: Util.Sum: null parameter list

This confirms that the first check in the method works.

4. Press the Enter key to close the program and return to Visual Studio 2012.

5. In the Code and Text Editor window, change the call to Console.WriteLine in doWork as shown here:

Console.WriteLine(Util.Sum());

This time, the method is called without any arguments. The compiler translates the empty argument list into an empty array.

6. On the DEBUG menu, click Start Without Debugging.

The program builds and runs, writing the following message to the console:

Exception: Util.Sum: empty parameter list

This confirms that the second check in the method works.

7. Press the Enter key to close the program and return to Visual Studio 2012.

8. Change the call to Console.WriteLine in doWork as follows:

Console.WriteLine(Util.Sum(10, 9, 8, 7, 6, 5, 4, 3, 2, 1));

9. On the DEBUG menu, click Start Without Debugging.

Verify that the program builds, runs, and writes the value 55 to the console.

10.Press Enter to close the application and return to Visual Studio 2012.

Comparing Parameter Arrays and Optional Parameters

In Chapter 3, you saw how to define methods that take optional parameters. At first glance, it appears there is a degree of overlap between methods that use parameter arrays and methods that take optional parameters. However, there are fundamental differences between them:

§ A method that takes optional parameters still has a fixed parameter list, and you cannot pass an arbitrary list of arguments. The compiler generates code that inserts the default values onto the stack for any missing arguments before the method runs, and the method is not aware of which of the arguments are caller provided and which are compiler-generated defaults.

§ A method that uses a parameter array effectively has a completely arbitrary list of parameters, and none of them has a default value. Furthermore, the method can determine exactly how many arguments the caller provided.

Generally, you use parameter arrays for methods that can take any number of parameters (including none), whereas you use optional parameters only where it is not convenient to force a caller to provide an argument for every parameter.

There is one final question worth pondering. If you define a method that takes a parameter list and provide an overload that takes optional parameters, it is not always immediately apparent which version of the method will be called if the argument list in the calling statement matches both method signatures. You will investigate this scenario in the final exercise in this chapter.

Compare a params array and optional parameters

1. Return to the ParamsArray solution in Visual Studio 2012, and display the Util.cs file in the Code and Text Editor window.

2. Add the following Console.WriteLine statement shown in bold to the start of the Sum method in the Util class:

3. public static int Sum(params int[] paramList)

4. {

5. Console.WriteLine("Using parameter list");

6. ...

}

7. Add another implementation of the Sum method to the Util class. This version should take four optional int parameters, each with a default value of 0. In the body of the method, output the message “Using optional parameters” and then calculate and return the sum of the four parameters. The completed method should look like this:

8. public static int Sum(int param1 = 0, int param2 = 0, int param3 = 0, int param4 = 0)

9. {

10. Console.WriteLine("Using optional parameters");

11. int sumTotal = param1 + param2 + param3 + param4;

12. return sumTotal;

}

13.Display the Program.cs file in the Code and Text Editor window.

14.In the doWork method, comment out the existing code and add the following statement:

Console.WriteLine(Util.Sum(2, 4, 6, 8));

This statement calls the Sum method, passing four int parameters. This call matches both overloads of the Sum method.

15.On the DEBUG menu, click Start Without Debugging to build and run the application.

When the application runs, it displays the following messages:

Using optional parameters

20

In this case, the compiler generated code that called the method that takes four optional parameters. This is the version of the method that most closely matches the method call.

16.Press Enter and return to Visual Studio.

17.In the doWork method, change the statement that calls the Sum method and remove the final argument (8), as shown here:

Console.WriteLine(Util.Sum(2, 4, 6));

18.On the DEBUG menu, click Start Without Debugging to build and run the application.

When the application runs, it displays the following messages:

Using optional parameters

12

The compiler still generated code that called the method that takes optional parameters, even though the method signature does not exactly match the call. Given a choice between a method that takes optional parameters and a method that takes a parameter list, the C# compiler will use the method that takes optional parameters.

19.Press Enter and return to Visual Studio.

20.In the doWork method, change the statement that calls the Sum method again and add two more arguments:

Console.WriteLine(Util.Sum(2, 4, 6, 8, 10));

21.On the DEBUG menu, click Start Without Debugging to build and run the application.

When the application runs, it displays the following messages:

Using parameter list

30

This time there are more arguments than the method that takes optional parameters specifies, so the compiler generated code that calls the method that takes a parameter array.

22.Press Enter and return to Visual Studio.

Summary

In this chapter, you learned how to use a params array to define a method that can take any number of arguments. You also saw how to use a params array of object types to create a method that accepts any number of arguments of any type. In addition, you saw how the compiler resolves method calls when it has a choice between calling a method that takes a parameter array and a method that takes optional parameters.

§ If you want to continue to the next chapter

Keep Visual Studio 2012 running, and turn to Chapter 12.

§ If you want to exit Visual Studio 2012 now

On the FILE menu, click Exit. If you see a Save dialog box, click Yes and save the project.

Chapter 11 Quick Reference

To

Do this

Write a method that accepts any number of arguments of a given type

Write a method whose parameter is a params array of the given type. For example, a method that accepts any number of bool arguments is declared like this:

someType Method(params bool[] flags)

{

...

}

Write a method that accepts any number of arguments of any type

Write a method whose parameter is a params array whose elements are of type object. For example:

someType Method(params object[] paramList)

{

...

}