This wouldn't be surprising if you understood how optional arguments are handled internally: they are inlined during compilation.
In other words, at the place where a method is called, any optional arguments are passed by the compiler - if you're calling an interface method, the compiler has no idea that there's an implementation with a different optional argument. The difference is best seen with code like this:
IMyInterface interface = new MyClass();
MyClass theClass = (MyClass)interface;
interface.PrintOpt(); // false
theClass.PrintOpt(); // true
Which is compiled to this (translated back to C#):
interface.PrintOpt(false);
theClass.PrintOpt(true);
The "default" arguments are no longer "default" in the IL code - they are just another explicitly passed argument.
If you want to use optional arguments that are overridable, just use method overloads. Or better, use default values that don't mean anything (e.g. null
or default(int?)
) and do any replacing for defaults inside of the method. This is in line with the original reason for including optional arguments to C# in the first place - VB-style COM interfaces often have methods with dozens of arguments, almost all of which are optional. Until now, when you wanted to call a method like this, you had to do something like
comInterface.MyMethod(TheActualArgumentICareAbout, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, ...);
Now you can just do
comInterface.MyMethod(argument, anotherSuperUseful: true);
This distinction is a big deal - it also means that you shouldn't ever change default arguments on any public method. Anyone using your library without recompiling will still use the old default value. It's similar to how const
values or enums are handled. If you use a null
for a default instead, the actual default value will be inside the method itself, and all callers will "see" the correct default even if they don't recompile (similar to using a public static readonly
field instead of const
, or a class with properties instead of an enum).