Versioning Issues With Optional Arguments

code 0 comments suggest edit

One nice new feature introduced in C# 4 is support for named and optional arguments. While these two features are often discussed together, they really are orthogonal concepts.

Let’s look at a quick example of these two concepts at work. Suppose we have a class with one method having the following signature.

  // v1
  public static void Redirect(string url, string protocol = "http");

This hypothetical library contains a single method that takes in two parameters, a required string url and an optional string protocol.

The following shows the six possible ways this method can be called.

HttpHelpers.Redirect("https://haacked.com/");
HttpHelpers.Redirect(url: "https://haacked.com/");
HttpHelpers.Redirect("https://haacked.com/", "https");
HttpHelpers.Redirect("https://haacked.com/", protocol: "https");
HttpHelpers.Redirect(url: "https://haacked.com/", protocol: "https");
HttpHelpers.Redirect(protocol: "https", url: "https://haacked.com/");

Notice that whether or not a parameter is optional, you can choose to refer to the parameter by name or not. In the last case, notice that the parameters are specified out of order. In this case, using named parameters is required.

The Next Version

One apparent benefit of using optional parameters is that you can reduce the number of overloads your API has. However, relying on optional parameters does have its quirks you need to be aware of when it comes to versioning.

Let’s suppose we’re ready to make version two of our awesome HttpHelpers library and we add an optional parameter to the existing method.

// v2
public static void Redirect(string url, string protocol = "http",   bool permanent = false);

What happens when we try to execute the client without recompiling the client application?

We get a the following exception message.

Unhandled Exception: System.MissingMethodException: Method not found: 'Void HttpLib.HttpHelpers.Redirect(System.String, System.String)'....

Whoops! By changing the method signature, we caused a runtime breaking change to occur. That’s not good.

Let’s try to avoid a runtime breaking change by adding an overload instead of changing the existing method.

// v2.1
public static void Redirect(string url, string protocol = "http");
public static void Redirect(string url, string protocol = "http",   bool permanent = false);

Now, when we run our client application, it works fine. It’s still calling the two parameter version of the method. Adding overloads is never a runtime breaking change.

But let’s suppose we’re now ready to update the client application and we attempt to recompile it. Uh oh!

The call is ambiguous between the following methods or properties: 'HttpLib.HttpHelpers.Redirect(string, string)' and 'HttpLib.HttpHelpers.Redirect(string, string, bool)'

While adding an overload is not a runtime breaking change, it can result in a compile time breaking change. Doh!

Talk about a catch-22! If we add an overload, we break in one way. If we instead add an argument to the existing method, we’re broken in another way.

Why Is This Happening?

When I first heard about optional parameter support, I falsely assumed it was implemented as a feature of the CLR which might allow dynamic dispatch to the method. This was perhaps very naive of me.

My co-worker Levi (no blog still) broke it down for me as follows. Keep in mind, he’s glossing over a lot of details, but at a high level, this is roughly what’s going on.

When optional parameters are in use, the C# compiler follows a simple algorithm to determine which overload of a method you actually meant to call. It considers as a candidate *every* overload of the method, then one by one it eliminates overloads that can’t possibly work for the particular parameters you’re passing in.

Consider these overloads:

public static void Blah(int i);
public static void Blah(int i, int j = 5);
public static void Blah(string i = "Hello"); 

Suppose you make the following method call: Blah(0).

The last candidate is eliminated since the parameter types are incorrect, which leaves us with the first two.

public static void Blah(int i); // Candidate
public static void Blah(int i, int j = 5); // Candidate
public static void Blah(string i = "Hello");  // Eliminated

At this point, the compiler needs to perform a conflict resolution. The conflict resolution is very simple: if one of the candidates has the same number of parameters as the call site, it wins. Otherwise the compiler bombs with an error.

In the case of Blah(0), the first overload is chosen since the number of parameters is exactly one.

public static void Blah(int i); //WINNER!!!
public static void Blah(int i, int j = 5);
public static void Blah(string i = "Hello"); 

This allows you to take an existing method that doesn’t have optional parameters and add overloads that have optional parameters without breaking anybody (except in Visual Basic which has a slightly different algorithm).

But what happens if you need to version an API that already has optional parameters?  Consider this example:

public static void Helper(int i = 2, int j = 3);            // v1
public static void Helper(int i = 2, int j = 3, int k = 4); // added in v2

And say that the call site is Helper(j: 10). Both candidates still exist after the elimination process, but since neither candidate has exactly one argument, the compiler will not prefer one over another. This leads to the compilation error we saw earlier about the call being ambiguous.

Conclusion

The reason that optional parameters were introduced to C# 4 in the first place was to support COM interop. That’s it. And now, we’re learning about the full implications of this fact.

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. Your only recourse in this case is to write a new method with a new name.

So be aware of this if you plan to use optional arguments in your APIs.

UPDATE: By the way, you can add overloads that have additional required parameters. So in this way, you are in the same boat as before. However, this can lead to other subtle versioning issues as my follow-up post describes.

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

Comments

avatar

22 responses

  1. Avatar for Hugo Freitas
    Hugo Freitas August 9th, 2010

    I liked a lot of optional parameters, but now I will reconsider using the same

  2. Avatar for ali62b
    ali62b August 10th, 2010

    Yep,SHOULD be very CAREFUL when using this new feature of .NET 4.

  3. Avatar for Jeeva
    Jeeva August 10th, 2010

    Good Point Phil.. we had faced the same problem when our own framework adds more optional paramters, and our application breaks at runtime. but, we rebuild the apps to make it work.

  4. Avatar for Bret Ferrier
    Bret Ferrier August 10th, 2010

    I don't know the whole thing felt a little bit too much like VB for me. I do however like when people use named parameters as it is often less clear to read.
    Email.SendMail("this is is","what is it","email@email.com")
    as opposed to
    Email.SendMail(Body: "this is is", Subject: "what is it", Email: "email@email.com")

  5. Avatar for Gobiner
    Gobiner August 10th, 2010

    Another versioning issue about optional parameters and much more subtle: the default values get copied to the call site. This means if you release a new version of your assembly and change the default values, every assembly that depends on it will need to be recompiled to get the new default values. If dependent assemblies are not recompiled, they will continue to call the methods with the old default values.

  6. Avatar for Bertrand Le roy
    Bertrand Le roy August 10th, 2010

    That's for public APIs that are destined to be used by random people. At Microsoft, we tend to think it's the whole world because it's our world, but from within the context of a single self-contained project, things are much different: breaking changes don't matter that much because you'll recompile everything that depends on your API at the same time as the API itself.

  7. Avatar for Chad Myers
    Chad Myers August 10th, 2010

    I remember having arguments with the VB guys back in the .NET 1.0 days and saying how evil optional arguments would become if they were allowed and praising Anders and C# for not making that mistake.
    I guess eventually the Office team either pulled rank on Anders, or he finally got tired of fighting them :(
    I think this will decision (to include optional params in C# in .NET 4) will go down as one of the worst decisions in the language. Strong words, I know, but the impact of it is also pretty strong.

  8. Avatar for Seth
    Seth August 10th, 2010

    I have always concluded that default parameter values are evil in an OO/Interface based world. In C++, this has always been ambiguous or at least surprising to the user, equivalent snippet in C# (uncompiled :)):
    <pre>
    class A
    {
    public virtual int test(int i=3);
    };
    class B : class A
    {
    public override int test(int i=5);
    }
    class App
    {
    static int main(int argc, string[] argv)
    {
    B t = new A();
    return t.test();
    }
    }
    </pre>
    PS no preview, so excuse the mess if it happens

  9. Avatar for Dorian Farrimond
    Dorian Farrimond August 10th, 2010

    I found this really informative. I started as a C# developer and moved across to VB.NET 2 years ago. At first I hated VB's optional parameters and thought overloads were better as they are a more defined statement of the intended use of the method.
    Gradually I've started to use VB.NET's optional parameters more, but it's interesting to read that they were added to C# for a specific purpose and are not necessarily intended for general use.
    I guess it's now the same situation with C# as VB.NET - think carefully before using. It's often better to avoid them I think.

  10. Avatar for Vijay Santhanam
    Vijay Santhanam August 10th, 2010

    Thanks for illustrating this. I like optional parameters, but will use them carefully for public APIs that change a lot

  11. Avatar for mattmc3
    mattmc3 August 10th, 2010

    @Dorian - I too am a C# guy living in a VB world at my current job. I avoided Optional parameters for awhile as well, favoring overloads, but the truth is that I eventually came around and now believe that Optional parameters are more readable and use them often, but only for one specific scenario - if one method calls another method just to supply the additional parameter. For example:

    Public Sub DoIt(ByVal s As String)
    DoIt(s, 3)
    End Sub
    Public Sub DoIt(ByVal s As String, ByVal i As Integer)
    ' Whatever....
    End Sub

    This code is so much more verbose and less readable than just doing this...

    Public Sub DoIt(ByVal s As String, Optional ByVal i As Integer = 3)
    ' Whatever....
    End Sub

    Now that C# has this, I think the same rule applies. Only use optional params if you're just overloading methods that call each other. Or, if you're making COM calls, but seriously - is that really a huge segment of the market anymore other than perhaps for Office automation... Microsoft could make that go away by providing a CLR API with a version of Office any day now... seriously MS, where is it?

  12. Avatar for Craig
    Craig August 10th, 2010

    I think Bertrand's comments are right on. If you're in a framework group at your employer or building a public API for resale the points made in the blog should absolutely be heeded. However, I believe most developers are building full applications (either in-house or for sale) and are compiling the entire application (libraries and all) when they do a build. At least that's what I've been doing for the ten years I've been using .NET. I know I'm not the whole world, but from my experience there are more shops like the ones I've worked in than not. In that case, it really doesn't matter, because you control both the API and the client consuming the API, and optional parameters make for a more readable API than dozens of overloads that try to capture all the different ways a method might need to be called.

  13. Avatar for tobi
    tobi August 11th, 2010

    is is important to distinguish between library code and internal code of which you have the source and _can_ refactor. then it is not a problem.

  14. Avatar for Kiran
    Kiran August 12th, 2010

    Though I am aware of optional keyword addition, not aware of how it worked internally and drawbacks in using them. Thanks for explaining so meticulously.

  15. Avatar for sanya
    sanya August 18th, 2010

    Its really nice information I Am highly obliged to you. Thanks.

  16. Avatar for AndyC
    AndyC August 18th, 2010

    Isn't the example a bit contrived? If you're adding a new overload, you wouldn't need the new parameter to be optional since you already have an overload for what happens if it's omitted. You can always refactor the code behind the scenes so that the 'old' method signature calls into your new one with the new paramter forced to it's default value (i.e. do it the old way)

  17. Avatar for Bil Simser
    Bil Simser October 7th, 2010

    Thanks for spelling this out. I'm with Chad and think this is the worst feature ever, and the fact it only exists for COM interop makes it even more bitter in my mouth. COM needs to die a horrible death in front of everyone. So what's the next big thing for the C# language? Multiple inheritance. Yikes. Optional arguments IMHO are just a bad design and I would throw up the code smell flag in a code review if someone brought them out. I would have to call into question why it's needed, and it better be a damn good reason and not "I want to use the new framework features".

  18. Avatar for dotnetchris
    dotnetchris August 14th, 2012

    This is such a gigantic failure on the part of the C# compiler / JIT team.
    These changes should be non breaking. It should just pick the closest method unless it really has no choice at all.

  19. Avatar for Sam
    Sam June 4th, 2015

    Yup, When I reviewer I sat down and were discussing over optional param and an overload, he suggested optional param, as it required less changes, and I was saying overload as it was suggesting a different version of same functionality.
    Ultimately we got a runtime ex. on the field. and well it was a disaster of us....

  20. Avatar for dingx
    dingx January 22nd, 2016

    Why this"While adding an overload is not a runtime breaking change, it can result in a compile time breaking change. Doh!"
    from the following example of Blah(0), it should be OK.

  21. Avatar for Xiaoguo Ge
    Xiaoguo Ge April 18th, 2016

    Why is recompiling after changing a function signature is an issue. Shouldn't it be expected. If recompiling is always required, then I see no problem with optional function arguments.

  22. Avatar for haacked
    haacked April 18th, 2016

    Do you have to recompile every one of your apps when you upgrade Windows? Or update the .NET Framework? Imagine if you did. That's the issue.