How Optional Arguments in C# 4 Actually Work
In my last post, I discussed one of my favorite new features in C# – Optional and Named Arguments. Although they are very easy to use, and quite intuitive, I have seen few discussions which explain how they actually work. Understanding how they are implemented helps in understanding the repercussions of using these features in development.
Optional Arguments are implemented by the compiler in a very simple way. They rely and are implemented using two attributes which already existed in the framework: System.Runtime.InteropServices.OptionalAttribute and System.Runtime.InteropServices.DefaultParameterValueAttribute.
Let’s take our example class constructor from the previous post:
class Test { public Test(string one = "1", string two = "2", string three = "3", string four = "4") {
When the compiler implements the above line, it is equivalent to the following:
// using System.Runtime.InteropServices; class Test { public Test( [Optional, DefaultParameterValue("1")] string one, [Optional, DefaultParameterValue("2")] string two, [Optional, DefaultParameterValue("3")] string three, [Optional, DefaultParameterValue("4")] string four) {
The added attributes flag, in the type information for the constructor, that the argument is optional, and provide a default value. If we analyze the IL for this, we see this information is compiled into:
.method public hidebysig specialname rtspecialname instance void .ctor([opt] string one, [opt] string two, [opt] string three, [opt] string four) cil managed { .param [1] = string('1') .param [2] = string('2') .param [3] = string('3') .param [4] = string('4') .maxstack 8 L_0000: ldarg.0 L_0001: call instance void [mscorlib]System.Object::.ctor() ...
The C# compiler flags each argument with [opt], and then sets metadata (.param) for each argument with the default value for that parameter. This is the same way VB.NET has always implemented Optional Arguments.
Since this information is available as meta data on the type, the C# compiler can easily query this, and see the default value at compile time. The compiler reads the default values, and supplies them for you. So, for example, saw we construct a Test object as follows:
Test test = new Test(two: "Two");
The C# compiler looks at this constructor, and sees that the constructor takes four arguments. However, they are all flagged as Optional. It then starts “filling in†the arguments, using the named argument to provide a value for “twoâ€, and the default value metadata for the “oneâ€, “threeâ€, and “four†parameters. This gets treated by the compiler exactly as if you typed:
Test test = new Test("1", "Two", "3", "4");
The important thing to realize here is that the compiler embeds the metadata into the construction call at compile time, “burning†the default values into place in the IL that is generated.
This is leads us to the main, important thing to realize when we use optional arguments. If we later change the default parameter value for an optional argument, we need to recompile all of the types that use that method, constructor, delegate, or indexer. If we do not, the type that’s using our class will still be compiled with the old default value in place.
Great explanation.
Easy to read and understand.
Thank you
Thank you for this informative article and examples of how this all works. It helped me to solve a confusing issue and provide feedback to my team.