Optional parameters – Conclusion: Treat like “unsafe”
This behavior rattled me quite a bit:
- public class Derived : Base
- {
- public override string Test(int test = 2)
- {
- return "Derived " + test.ToString();
- }
- }
- public class Base : IBase
- {
- public virtual string Test(int test = 1)
- {
- return "base " + test.ToString();
- }
- }
- interface IBase
- {
- string Test(int test = 3);
- }
- class Program
- {
- static void Main(string[] args)
- {
- Console.WriteLine(b.Test());
- IBase bi = b;
- Console.WriteLine(bi.Test());
- Console.WriteLine(d.Test());
- Base d2 = d;
- Console.WriteLine(d2.Test());
- IBase d2i = d2;
- Console.WriteLine(d2i.Test());
- Console.Read();
- }
- }
Would you have guessed that the output would be:
base 1
base 3
Derived 2
Derived 1
Derived 3
This is wrong on so many levels; I can't even start to tell you how appalled I am. Horrible! The debugging nightmares this will bring upon us... Because it is very counter intuitive!
Plus let's not forget call site integration and that whole bag of versioning problems...
Looking at all these potentially harmful side effects, I propose the following: Treat optional parameters like unsafe regions. Create a compiler flag that is off by default and must be set in order to allow the compiler to evaluate optional parameters - and by that make it a conscious choice for developers to go down that dangerous path. Plus to show people using a library with optional parameters: Buyer beware!
Seriously, think about it. Developer from all skill sets could benefit from said compiler flag.
- Newbies wouldn't know how to set the compiler flag - and would use regular (safe) overloads for their programming tasks. And by that they would be shielded from subtile bugs that might creep into their code that will utterly confuse them.
- I as a more advanced programmer would like to disable them when using an external API. Don't even bother allowing me to not set optional parameters. I am not that lazy, honestly. Maintainability is extremely important for me and I'd rather type in a couple of parameters than potentially getting into this debugging nightmare.

Hi Tobi,
This does look terrible to me. Having known that VB.NET has had the optional parameter feature for a long time, I decided to try the same piece of code in VB.NET and see what happens. Darn, I just don’t know enough VB to get it right for the first time.
Class Derived
Inherits Base
Public Overrides Function Test( Optional t As Integer = 1 ) As String
Return “Derived ” + t.ToString()
End Function
End Class
Class Base
Implements IBase
Public Overridable Function Test( Optional t As Integer = 1 ) As String Implements IBase.Test
Return “Base ” + t.ToString()
End Function
End Class
Interface IBase
Function Test( Optional t As Integer = 1 ) As String
End Interface
Module Program
Sub Main()
Dim b As Base = New Base()
System.Console.WriteLine( b.Test() )
Dim bi As IBase = b
System.Console.WriteLine( bi.Test() )
Dim d As Derived = New Derived()
System.Console.WriteLine( d.Test() )
Dim d2 As Base = d
System.Console.WriteLine( d2.Test() )
Dim d2i As IBase = d2
System.Console.WriteLine( d2i.Test() )
End Sub
End Module
VB.NET’s compiler will complain if the default values of optional parameters don’t match, which eliminates the problem you had in the C# 4 code. But then of course, you won’t be able to use different default values in the inheritance chain, which might be bad, or good, depends on what you’re trying to do.
Not sure whether creating a compiler flag for optional parameters is a good idea. Maybe for safety sakes we should suggest C# 4’s optional parameters to behave similiar to VB.NET’s…
- RednaxelaFX (Kris Mok)
Comment on November 11, 2008 @ 09:27:55
Hey Kris,
good call, trying out the VB version. And it makes more sense when looking at the VB implementation of the feature. That does get rid of a lot of the problems. Maybe since we are looking at the CTP of C# 4.0 they will “fix” it down the line.
Cheers,
Tobi
Comment on November 11, 2008 @ 18:38:19
Hey,
So I tried the same code in C++, and surprisingly (or maybe not), the behavior is exactly the same as C# 4 in CTP. Is this a tradition or something…
Cheers,
- RednaxelaFX (Kris Mok)
Comment on November 12, 2008 @ 05:36:15
This behavior makes sense.
Optional parameters (at least in C++, etc) are automatically filled in at compile time, by using the type of the variable that the method is on. The inheritance tree and such are not considered.
For the same reason if you had the following, you would not be able to use optional parameters on Derived2.Test, even though Base.Test supports it:
public class Derived2 : Base
{
public override string Test(int test)
{
return “Derived ” + test.ToString();
}
}
Comment on January 28, 2009 @ 00:23:56
The output is exactly as I expected. Optional parameters are not polymorphic. The compiler inserts them at compile time which means that you’ll get the default value declared with the only default known at compile time. Making default values work at runtime would be crazy and hurt performance.
Like any other feature, as long as you know what is really happening, it makes sense. Default values are only there for convenience and the default you get will be exactly the default you know about at compile time (the default as declared on the variable type you are calling upon). This also means that intellisense will be able to let you know what value is is substituting as the default for you.
Comment on January 31, 2009 @ 02:24:31
Hi Tum,
I’m glad that the style suits you. Though I would argue that it is a direct violation of the Liskov substitution principle. Just imagine:
var myVariable = Api.GetMyVariable();
var myMethodResult = myVariable.GetResult();
And GetResult() being defined in IMyVariable as GetResult(int test = 2) and in MyVariable as GetResult(int test = 1)
Now the Api.GetMyVariable() changes from IMyVariable GetMyVariable() to MyVariable GetMyVariable() which is a valid substitution.
Unfortunatelly your var myMethodResult = myVariable.GetResult(); now changes its meaning without you even noticing.
Fun!
Comment on February 12, 2009 @ 14:08:26