C# Optional Parameters in Library Code
C# optional parameters provide a handy way to create methods that can be called with a varying number of arguments but without the need to create overloaded versions. However, the way that they are used in referenced libraries can cause problems.
With C# version 4.0, Microsoft introduced the concept of optional parameters to the language. Defined within the signature of a method, an optional parameter includes a default value. When the method is called, and the argument is omitted, the default value is used instead. This allows you to write methods that can be called in many different ways without the need to create as many, if any, overloaded versions.
Optional parameters can be very useful when they are used in a single project solution and only called from within the project. However, if you create a class library, or any other project type, which contains public methods with optional parameters, you can see unexpected behaviour. This occurs when projects that reference assemblies containing optional arguments are not recompiled after a default parameter value is changed.
What's the Problem?
Imagine you compile a dynamic link library (DLL) containing methods with default parameter values. The default values are stored within the DLL, as you would expect. The problem arises when another assembly uses the methods. The .NET compilers are permitted to create copies of any default parameter values in the referencing assemblies and call the external methods as if they did not include an optional parameter at all. If you change the defaults in the DLL and do not recompile the executable, the old default values can be used instead of the new ones.
As you might imagine, this can introduce subtle bugs into your code. You might expect a method to be called with one default value when, in fact, it uses an out-of-date value instead. Incidentally, you can encounter the same problem when reading publicly visible constants from a separate assembly.
In the remainder of this article we'll see a step-by-step way to recreate the problem.
Recreating the Problem
To begin, we need to create a class library containing a method with an optional parameter. Open Visual Studio and create a new class library project named, "LibraryCodeWithDefaults". Add the following class to the project. The ShowMessage method simply outputs the value from the optional parameter. It will allow us to see which default value is being used when we call the ShowMessage method with no arguments.
public class Test
public void ShowMessage(string msg = "Hello, world!")
Save the project and compile it into a DLL. Once saved and compiled, close the project. We will create a second project in a separate solution so that Visual Studio doesn't compile both together, masking the problem.
For the next step, create a new console application named, "CallerWithDefaults". Add a reference to the compiled "LibraryCodeWithDefaults.dll" file and add the following using directive to the class that contains the Main method.
Next, add the following code to the Main method. This shows the message from the DLL.
Save the project, compile it and close Visual Studio. To run the program and see the output, use Windows Explorer to locate the "CallerWithDefaults.exe" file. Double-click the file. You should see the following output, verifying that the code is working as anticipated.
We can now recreate the problem by changing the DLL without recompiling the executable. Re-open the LibraryCodeWithDefaults project and modify the default value of the "msg" parameter, as follows:
public class Test
public void ShowMessage(string msg = "Goodbye, cruel world!")
Recompile the project and close Visual Studio. As the folder containing the executable now has an out of date DLL, copy the newly compiled version of LibraryCodeWithDefaults.dll from the LibraryCodeWithDefaults bin folder and overwrite the DLL in the CallerWithDefaults bin folder. This simulates the situation where a new version of a DLL is distributed to users without sending an updated executable.
You might expect that running the program would now show the new default value. After all, we've updated the DLL containing the default and when we call the method from the executable we aren't passing a value to the parameter. However, if you double-click the "CallerWithDefaults.exe" file you still see the original message:
To ensure that the console application uses the correct value, you need to recompile it. This copies the new default value into the executable.
Unfortunately the above behaviour means that optional parameters are not a good solution for shared libraries unless there is no chance that you will change the default value. This is a rare situation. If you need to provide the ability to call shared methods with optional parameters you should instead manually create overloaded versions of your methods.
NB: If you use code analysis with the appropriate rules enabled, you will be shown a warning if you create a method with a default parameter. The warning is "CA1026: Default parameters should not be used".
18 January 2013