Optional parameters may appear in the middle of the parameter list

 
 
  • Gérald Barré

In .NET, optional parameters are not always the last parameters. While C# does not allow the declaration of optional parameters in the middle of the parameter list, it is possible to do so in IL or from other languages like VB.NET or F#. Also, the compiler may lower some features and create methods with optional parameters in the middle of the parameter list.

Why does it matter? If you write a source generator that generates new methods with the same parameters as another method, you may need to generate methods with optional parameters in the middle of the parameter list. While these methods are not common, they are valid in .NET and can be useful in some scenarios.

To better understand the issue, let's start with a simple example. The following code is not valid in C#:

C#
// Not allowed in C#
// CS1737 Optional parameters must appear after all required parameters
void Sample(int a = 1, int b)
{
}

When compiling a method with optional parameters, the compiler replaces the C# syntax with 2 attributes: [Optional] and [DefaultParameterValue]. The [Optional] attribute is used to mark a parameter as optional, and the [DefaultParameterValue] attribute is used to set the default value. The following code is valid in C#:

C#
// Call the method without providing the optional parameter
// Prints 42
Sample(b: 2);

// Declare the "a" parameter as optional with a default value of 40
void Sample([Optional, DefaultParameterValue(40)]int a, int b)
{
    Console.WriteLine(a + b);
}

Note that the C# compiler is more restrictive than other languages in the allowed values in [DefaultParameterValue]. It ensures the value type is the same as the parameter type, but it may not allow some valid conversions. For instance, the following code would be valid in VB.NET or F#, but not in C#:

C#
// CS1908 The type of the argument to the DefaultParameterValue attribute must match the parameter type
void Sample([Optional, DefaultParameterValue(default)] CancellationToken cancellationToken)
{
}

The same method in VB.NET is valid:

VB.NET
' Ok in VB.NET
Public Class Class1
    Public Sub Sample(<[Optional], DefaultParameterValue(Nothing)> cancellationToken As CancellationToken)
    End Sub
End Class

The same method in F# is also valid:

F#
// Ok in F#
type Class1 =
    static member Sample([<Optional; DefaultParameterValue(CancellationToken())>] cancellationToken: CancellationToken) =
        ()

Side note: Even the C# compiler may generate a method with optional parameters in the middle of the parameter list. As Jason Bock explained on Mastodon, when creating an indexer with optional parameters, the compiler generates a method with an optional parameter in the middle of the parameter list:

C#
 // Creates a method (set_Item) with an optional parameter in the middle of the parameter list
 // int set_Item(int a, int b = 2, int value)
public int this[int a, int b = 2]
{
    set { }
}

The previous code will generate the following IL:

MSIL
    .method public hidebysig specialname
        instance void set_Item (
            int32 a,
            [opt] int32 b, // <--- optional parameter in the middle of the parameter list
            int32 'value'
        ) cil managed
    {
        .param [2] = int32(2)
        // Method begins at RVA 0x2069
        // Code size 2 (0x2)
        .maxstack 8

        IL_0000: nop
        IL_0001: ret
    } // end of method C::set_Item

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub