2.15. Variable-Length Parameter Lists

Our message() function of the preceding section is actually something of a problem. It represents the collection of values a user may wish to display. The problem is that the type and number of these values of interest to users are limitless. Trying to accommodate even a subset of all the requested variations soon begins to drive us crazy.

A more manageable solution is to declare a signature that can be passed an arbitrary number of arguments of possibly arbitrary types. We do this through the params variable-length parameter, which is characterized as follows:

  • The params keyword identifies that we are passing a variable number of parameters.

  • An array of the type of arguments we wish to accept follows the params keyword. This array holds the actual arguments that are being passed. If we wish to accept heterogeneous types, we declare the array to be of type object.

For example, here is a revised set of message() functions illustrating uses of the params keyword:

class MessageHandler
{
   // our fixed-length parameter instances
   public void message(){ ... }
   public void message( char ch )   { ... }
   public void message( string msg ){ ... }
   // our variable-length parameter instances
   public void message(string msg, params int[] args)   {...}
   public void message(string msg, params double[] args){...}
   public void message(string msg, params object[] args){...}
   // ... rest of class
}

The first three instances take a fixed number of parameters: The first takes no parameters, the second a single parameter of type char, the third a single parameter of type string. Here are some examples of their invocation:

MessageHandler mh = new MessageHandler( file );

mh.message();
mh.message( 'a' );
mh.message( "hello" );

Each of these calls exactly matches the parameter list of one of the first three overloaded instances of message(). There is nothing surprising in these invocations. The next three instances are what become interesting.

For example, the following invocations all match the instance of message() that declares an integer params array:

int ival= 10, dval = 0;

// all match: message( string, params int[] )
mh.message( "mumble", 10, ival, dval, 1024 );
mh.message( "mumble", ival );
mh.message( "fib: ", 1,1,2,3,5,8,13,21,34,55 );

The same invocations, but with arguments of type double rather than int, match the instance of message() that declares a double params array. What happens if we mix the types of values? For example, the call

mh.message( "mix types", 10, 3.14159 );

mixes types of int and double. Which function best matches the call? In this case, the second value, 3.14159, is of type double. A double is not implicitly converted to type int, so the integer params array instance is not a viable function. The double params array instance is selected, and the first numeric value, 10, is implicitly promoted to double.

What if we introduce types for which no conversion to either int or double exists? For example, consider this call:

mh.message( "weird types", false, mh, 3.14, 2m );

None of the instances on the surface seem to match this call, yet it is handled successfully. The instance invoked is that of the object params array. The bool literal false, the MessageHandler object mh, a value of type double, and a value of type decimal are all implicitly converted to type object.

To find out the number of parameters passed in with each call, we query the array parameter about its length. If nonzero, we iterate across the array, accessing each argument in turn. (We would use type reflection to discover the actual type of each object. This is covered in Section 8.2.) For example, here is a possible implementation:

public void message( string msg, params object[] args )
{
   Console.WriteLine( msg );

   if ( args.Length != 0 )
          foreach ( object o in args )
                 Console.WriteLine( "	{0}", o.ToString() );
}

Alternatively, we can access each variable parameter through an explicit index into the array:

public void message( string msg, params int[] args )
{
      Console.WriteLine( msg );

      for ( int ix = 0; ix < args.Length; ++ix )
            Console.Write( "{0} ", args[ix].ToString() );
      Console.WriteLine();
}

A function signature can have only one params array. It must be declared last. The params array must be one-dimensional, and we cannot modify it with either the ref or the out keyword.

Typically, the use of a params array augments an existing set of function instances that take one or more explicit parameters, such as the definition of message(). In our example, we handle three conditions explicitly—no parameters, a single string parameter, and a single char parameter. All other invocations must start with a string, followed by zero or more arguments of any type.

If the params array follows one or more explicit parameters, as it does with our three message() instances, each of those explicit parameters must have an actual argument provided by the user. The params array handles all additional arguments, if any.

If we want to accept zero or more arguments of a particular type, we make the params array the only parameter of the function. To accept zero or more arguments of any type, we declare the params array to be of type object:

// accepts zero or more arguments of type int
static void func( params int [] args )

// accepts zero or more arguments of type string
static void func( params string [] args )

// accepts zero or more arguments of any type
static void func( params object [] args )

How does overloading resolution work in the presence of a params keyword? Consider the following two functions:

static void display( string msg )
static void display( string msg, int ix, params object [] args )

and the following invocation:

display( "message", 42, arg1, arg2 );

The first instance of display() is not a viable candidate because it takes a single parameter. The second instance may match.

The compiler evaluates the signature as follows: It tries to match by first not considering the params array. The explicit parameters of the function, in this case string and int, must match—either exactly or through an implicit conversion. If a match is achieved, the remaining arguments, if any, are packed into an array by the compiler and passed to the function. In this example, arg1 and arg2 become the first and second elements of the array.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.76.72