I am aware that adding an optional parameter in a library method is a breaking change,
void Foo(int x) // OLD void Foo(int x, int y = 5) // NEW
because in the compiled code the new version is seen as Foo(int, int)
. Every call of Foo(0)
(source code) is translated to Foo(0, 5)
(compiled code) by the compiler. Thus, an old client, using a compiled call of Foo(0)
would not find a suitable method.
What about the other direction?
void Foo(int x, int y = 5) { ... } // OLD void Foo(int x) { Foo(x, 5); } // NEW void Foo(int x, int y) { ... } // NEW
Foo(0)
(source code) would still compile, and Foo(0, 5)
(compiled code) would still find a suitable overload, so, theoretically, this should work.
Does it work in practice, i.e., is this scenario "officially supported" by the .NET runtime and the C#/VB compilers? Or are calls to methods with optional parameters somehow "marked", causing them to fail when the optional parameters are replaced by overloads?
EDIT: To clarify, I'm asking about binary compatibility: Is it possible to replace library.dll (old)
with library.dll (new)
without recompiling projectUsingLibrary.exe
?
If you have a method with optional parameters, you can never add an overload with additional optional parameters out of fear of causing a compile-time breaking change. And you can never remove an existing overload, as this has always been a runtime breaking change. You pretty much need to treat it like an interface.
The thing with optional parameters is, they are BAD because they are unintuitive - meaning they do NOT behave the way you would expect it. Here's why: They break ABI compatibility ! so you can change the default-arguments at one place.
What are Optional Parameters? By definition, an Optional Parameter is a handy feature that enables programmers to pass less number of parameters to a function and assign a default value.
A mandatory parameter must be explicitly given on the command-line, otherwise an error message will be printed to prompt the user to re-enter the command. If an optional parameter is not specified, the default is used. Note: Mandatory parameters have a default value for use in the GUI.
I thought that was a good question, so here goes my take.
Using a quick client that does this:
c1.Foo(1); c1.Foo(1, 2);
When using optional parameter the client IL looks like:
IL_0000: nop IL_0001: newobj instance void [ClassLibrary1]ClassLibrary1.Class1::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldc.i4.1 IL_0009: ldc.i4.5 IL_000a: callvirt instance void [ClassLibrary1]ClassLibrary1.Class1::Foo(int32, int32) IL_000f: nop IL_0010: ldloc.0 IL_0011: ldc.i4.1 IL_0012: ldc.i4.2 IL_0013: callvirt instance void [ClassLibrary1]ClassLibrary1.Class1::Foo(int32, int32) IL_0018: nop IL_0019: ret
and when using overloads it looks like:
IL_0000: nop IL_0001: newobj instance void [ClassLibrary2]ClassLibrary2.Class2::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldc.i4.1 IL_0009: callvirt instance void [ClassLibrary2]ClassLibrary2.Class2::Foo(int32) IL_000e: nop IL_000f: ldloc.0 IL_0010: ldc.i4.1 IL_0011: ldc.i4.2 IL_0012: callvirt instance void [ClassLibrary2]ClassLibrary2.Class2::Foo(int32, int32) IL_0017: nop IL_0018: ret
So, if you changed the implementation from optional to overloads, but left the client as it was originally, it would be effectively adding the default parameter for you, and always calling the the function that has two arguments, which may or may not be the desired behaviour.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With