C# Optional Parameters Gotcha

September 06, 2017

Optional parameters are a really useful feature in C#, but do you know how they actually work? They’re just syntactic sugar. If you call a method with an optional parameter and don’t specify a value then the compiler just hard-codes the default in there for you. This could lead to some problems or unexpected behavior if you’re calling methods with optional parameters across assemblies.

Let’s look at this very simple Employee class. I put it in it’s own class library called MyLibrary. It’s constructor requires the caller to specify the employee’s first and last name and has an optional parameter indicating whether the employee is full time or not. Employees are not full time by default.

using System;
namespace MyLibrary
{
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsFullTime { get; set; }
public Employee(string firstName, string lastName, bool isFullTime = false)
{
FirstName = firstName;
LastName = lastName;
IsFullTime = isFullTime;
}
}
}
view raw Employee.cs hosted with ❤ by GitHub

The code that will use this Employee class is in a console application called MyApp. MyApp references MyLibrary. The program just news up an Employee (without specifying a value for isFullTime) and prints out that Employee’s information.

using MyLibrary;
using System;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
var employee = new Employee("Jane", "Doe"); // Use the default parameter
Console.WriteLine($"Name: {employee.FirstName} {employee.LastName}; Full Time: {employee.IsFullTime}");
Console.ReadKey();
}
}
}
view raw Program.cs hosted with ❤ by GitHub

When I run it you can see that the default value for isFullTime was used to create the Employee.

Let’s use ildasm to look at the IL that the compiler generated for the Main method in MyApp.exe.

IL

The first thing the program does is push three values onto the stack: the first name (ldstr "Jane"), last name (ldstr "Doe"), and a 0 (ldc.i4.0) representing false. It then calls the Employee constructor which reads those values from the stack. That false value was hard-coded at compile time.

To show the unintended consequences that can cause this, let’s modify the Employee constructor to default isFullTime to true instead of false. Let’s also add a print statement in there just so we can show that the new version of the Employee constructor is being called.

using System;
namespace MyLibrary
{
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsFullTime { get; set; }
public Employee(string firstName, string lastName, bool isFullTime = true)
{
Console.WriteLine("Inside Employee constructor. isFullTime should default to true.");
FirstName = firstName;
LastName = lastName;
IsFullTime = isFullTime;
}
}
}
view raw Employee-2.cs hosted with ❤ by GitHub

Here’s the output if I rebuild only the class library and leave my executable (which should be using the default isFullTime value) untouched:

Default

The print statement is there but isFullTime is still set to false! I’m sure you can imagine the consequences of accidentally defaulting an employee to full time vs. part time in a real world application. This is a pretty rare scenario since 99.9% of the time any code calling this method would be rebuilt and the new default values would be substituted in by the compiler. The only real time this would be a problem is if you regularly “hot-swap” DLLs without re-compiling your own code (which you should probably stop doing). Either way, after demonstrating this behavior to my team we decided on a new standard to not use default parameters in any public APIs.


© 2020 Jesse Barocio. Built with Gatsby