The C# language designers also added support for optional parameters.7 By allowing the association of a parameter with a constant value as part of the method declaration, it is possible to call a method without passing an argument for every parameter of the method (see Listing 5.24).
In Listing 5.24, the DirectoryCountLines() method declaration with a single parameter has been removed (commented out), but the call from Main() (specifying one parameter) remains. When no extension parameter is specified in the call, the value assigned to extension within the declaration (*.cs in this case) is used. This allows the calling code to not specify a value if desired, and it eliminates the additional overload that would otherwise be required. Note that optional parameters must appear after all required parameters (those that don’t have default values). Also, the fact that the default value needs to be a constant, compile-time–resolved value is fairly restrictive. You cannot, for example, declare a method like
DirectoryCountLines(
string directory = Environment.CurrentDirectory,
string extension = "*.cs")
because Environment.CurrentDirectory is not a constant. In contrast, because "*.cs" is a constant, C# does allow it for the default value of an optional parameter.
A second method call feature is the use of named arguments.8 With named arguments, it is possible for the caller to explicitly identify the name of the parameter to be assigned a value, rather than relying solely on parameter and argument order to correlate them (see Listing 5.25).
In Listing 5.25, the call to DisplayGreeting() from within Main() assigns a value to a parameter by name. Of the two optional parameters (middleName and lastName), only lastName is given as an argument. For cases where a method has lots of parameters and many of them are optional,9 using the named argument syntax is certainly a convenience. However, along with the convenience comes an impact on the flexibility of the method interface. In the past, parameter names could be changed without causing C# code that invokes the method to no longer compile. With the addition of named parameters, the parameter name becomes part of the interface because changing the name would cause code that uses the named parameter to no longer compile.
For many experienced C# developers, this is a surprising restriction. However, the restriction has been imposed as part of the Common Language Specification ever since .NET 1.0. Moreover, Visual Basic has always supported calling methods with named arguments. Therefore, library developers should already be following the practice of not changing parameter names to successfully interoperate with other .NET languages from version to version. In essence, named arguments now impose the same restriction on changing parameter names that many other .NET languages already require.
Given the combination of method overloading, optional parameters, and named parameters, resolving which method to call becomes less obvious. A call is applicable (compatible) with a method if all parameters have exactly one corresponding argument (either by name or by position) that is type-compatible, unless the parameter is optional (or is a parameter array). Although this restricts the possible number of methods that will be called, it doesn’t identify a unique method. To further distinguish which specific method will be called, the compiler uses only explicitly identified parameters in the caller, ignoring all optional parameters that were not specified at the caller. Therefore, if two methods are applicable because one of them has an optional parameter, the compiler will resolve to the method without the optional parameter.
When the compiler must choose which of several applicable methods is the best one for a particular call, the one with the most specific parameter types is chosen. Assuming there are two applicable methods, each requiring an implicit conversion from an argument to a parameter type, the method whose parameter type is the more derived type will be used.
For example, a method that takes a double parameter is chosen over a method that takes an object parameter if the caller passes an argument of type int. This is because double is more specific than object. There are objects that are not doubles, but there are no doubles that are not objects, so double must be more specific.
If more than one method is applicable and no unique best method can be determined, the compiler issues an error indicating that the call is ambiguous.
For example, given the following methods
static void Method(object thing){}
static void Method(double thing){}
static void Method(long thing){}
static void Method(int thing){}
a call of the form Method(42) resolves as Method(int thing) because that is an exact match from the argument type to the parameter type. Were that method to be removed, overload resolution would choose the long version, because long is more specific than either double or object.
The C# specification includes additional rules governing implicit conversion between byte, ushort, uint, ulong, and the other numeric types. In general, though, it is better to use a cast to make the intended target method more recognizable.
________________________________________