More Versioning Fun With Optional Arguments

code 0 comments suggest edit

In my last blog post, I covered some challenges with versioning methods that differ only by optional parameters. If you haven’t read it, go read it. If I do say so myself, it’s kind of interesting. ;) In this post, I want to cover another very subtle versioning issue with using optional parameters.

At the very end of that last post, I made the following comment.

By the way, you can add overloads that have additional requiredparameters. So in this way, you are in the same boat as before.

However, this can lead to subtle bugs. Let’s walk through a scenario. Imagine that some class library has the following method in version 1.0.

public static void Foo(string s1, string s2, string s3 = "v1") {
    Console.WriteLine("version 1");
}

And you have a client application which calls this method like so:

ClassName.Foo("one", "two");

That’s just fine right. You don’t need to supply a value for the argument s3 because its optional. Everything is hunky dory!

But now, the class library author decides to release version 2 of the library and adds the following overload.

public static void Foo(string s1, string s3 = "v2") {
    Console.WriteLine("version 2");
}

public static void Foo(string s1, string s2, string s3 = "v1") {
    Console.WriteLine("version 1");
}

Notice that they’ve added an overload that only has two parameters. It differs from the existing method by one required parameter, which is allowed.

As I mentioned before, you’re always allowed to add overloads and maintain binary compatibility. So if you don’t recompile your client application and upgrade the class library, you’ll still get the following output when you run the application.

version 1

But what happens when you recompile your client application against version 2 of the class library and run it again with no source code changes. The output becomes:

version 2

Wow, that’s pretty subtle.

It may not seem so bad in this contrived example, but lets contemplate a real world scenario. Let’s suppose there’s a very commonly used utility method in the .NET Framework that follows this pattern in .NET 4. And in the next version of the framework, a new overload is added with one less required parameter.

Suddenly, when you recompile your application, every call to the one method is now calling the new one.

Now, I’m not one to be alarmist. Realistically, this is probably very unlikely in the .NET Framework because of stringent backwards compatibility requirements. Very likely, if such a method overload was introduced, calling it would be backwards compatible with calling the original.

But the same discipline might not apply to every library that you depend on today. It’s not hard to imagine that such a subtle versioning issue might crop up in a commonly used 3rd party open source library and it would be very hard for you to even know it exists without testing your application very thoroughly.

The moral of the story is, you do write unit tests dontcha? Well dontcha?!If not, now’s a good time to start.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

5 responses

  1. Avatar for James Manning
    James Manning August 12th, 2010

    Well, if we're going to talk about new versions of libraries breaking things, in case others haven't hit it, keep in mind that extension methods are similarly subtle in causing a breakage, except it's even more difficult to avoid causing a potential break.
    Before .NET 4 added Stream.CopyTo, I had done it as an extension method in my 3.5 projects - my impl was different than what .NET 4 ended up shipping, since I did basically if (CanSeek) Seek(0); copy stuff; if (CanSeek) Seek(0) (it was useful for me :)
    retarget .net 4, no compile warnings, and it breaks.
    every extension method you use is a potential for a breaking change if the thing you're extending adds that method since the extension method will then get silently ignored (I asked Eric to make it a warning, but he disagrees). The only thing the creator of an API can do is never add methods to any public surface (classes, interfaces, etc) since it could break anyone that's using extension methods (whether of their creation or some library they use).
    The BCL made what was a breaking change for me by adding CopyTo (obviously all new methods are in the same boat), but I don't think anyone in the BCL team would have considered it a breaking change then (or now :), but it still broke me :)

  2. Avatar for Macario F. Pedraza
    Macario F. Pedraza August 12th, 2010

    Interesting. I tried reproducing this in VB where I have a long history of using Optional (before it was cool ;):
    Public Sub Foo(ByVal s1 As String, ByVal s2 As String, Optional ByVal s3 As String = "v1")
    Console.WriteLine("Version 1")
    End Sub
    Public Sub Foo(ByVal s1 As String, Optional ByVal s3 As String = "v2")
    Console.WriteLine("Version 2")
    End Sub

    This code won't compile and brings up the message "[Foo1] and [Foo2] cannot overload each other because they differ only by optional parameters."
    This seems to me like a C# compiler design bug issue. They should probably consult with the VB guys.
    Thanks for pointing this out.

  3. Avatar for Jonathan van de Veen
    Jonathan van de Veen August 12th, 2010

    Ok, so it can introduce bugs.
    However I would consider two overloaded methods providing different functionality poor design / naming. You shouldn't overload a mehod with something that's very different because it's name should be different.

  4. Avatar for Jennifer
    Jennifer August 26th, 2010

    Having used JavaScript for many years I have always considered optional parameters as a necessary evil due to the lack of method overloading. In C# I don't ever use optional parameters. If I have a method where a parameter could be optional I create 2 overloaded methods outlining the different uses of the method. Much easier to understand and it avoids problems like these.

  5. Avatar for Alfred
    Alfred September 13th, 2010

    From this post and your previous post, it appears that 2 design flaws are identified:
    A) no compiler warning of conflicts between overloads and optional parameters (unlike VB)
    B) optional parameters are compiled to and persisted with the callers; not adapter pattern.
    Until enforcement can be built into the compiler, would you support the follow rules?
    1) not to mix overloaded methods with methods that use optional parameters, use only one or the other. (workaround for flaw A)
    2) if methods do use optional parameters, the caller must use named parameters in conjunction. (workaround for bug A)
    3) don't use optional parameters in public or protected methods; only allow use for private or internal. (workaround for flaw B)