Optional Parameters

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).

Listing 5.24: Methods with Optional Parameters
public static class LineCounter
{
    public static void Main(string[] args)
    {
        int totalLineCount;
 
        if(args.Length > 1)
        {
            totalLineCount =
                DirectoryCountLines(args[0], args[1]);
        }
        else if(args.Length > 0)
        {
            totalLineCount = DirectoryCountLines(args[0]);
        }
        else
        {
            totalLineCount = DirectoryCountLines();
        }
 
        Console.WriteLine(totalLineCount);
    }
 
    static int DirectoryCountLines()
    {
        // ...
    }
 
    /*
      static int DirectoryCountLines(string directory)
      { ... }
    */
 
    static int DirectoryCountLines(
        string directory, string extension = "*.cs")
    {
        int lineCount = 0;
        foreach(string file in
            Directory.GetFiles(directory, extension))
        {
            lineCount += CountLines(file);
        }
 
        foreach(string subdirectory in
            Directory.GetDirectories(directory))
        {
            lineCount += DirectoryCountLines(subdirectory);
        }
 
        return lineCount;
    }
 
    private static int CountLines(string file)
    {
        // ...
    }
}

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.

Guidelines
DO provide good defaults for all parameters where possible.
DO provide simple method overloads that have a small number of required parameters.
CONSIDER organizing overloads from the simplest to the most complex.

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).

Listing 5.25: Specifying Parameters by Name
public static void Main()
{
    DisplayGreeting(
        firstName: "Inigo", lastName: "Montoya");
}
 
public static void DisplayGreeting(
    string firstName,
    string? middleName = null,
    string? lastName = null
    )
{
    // ...
}

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.

Guidelines
DO treat parameter names as part of the API, and avoid changing the names if version compatibility between APIs is important.

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.

AdVanced Topic
Method Resolution

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.

________________________________________

7. Introduced in C# 4.0.
8. Introduced in C# 4.0.
9. A common occurrence when accessing Microsoft COM libraries such as Microsoft Word or Microsoft Excel.
{{ snackbarMessage }}