Optional arguments on both ends

Optional arguments on both ends

Rate This
  • Comments 23

Before we get into today's topic, a quick update on my posting from last year about Roslyn jobs. We have gotten a lot of good leads and made some hires but we still have positions open, both on the Roslyn team and on the larger Visual Studio team. For details, see this post on the Visual Studio blog. Again, please do not send me resumes directly; send them via the usual career site. Thanks!


Here's a recent question I got from a reader: what is the correct analysis of this little problem involving optional arguments?

static void F(string ham, object jam = null) { }
static void F(string spam, string ham, object jam = null) { }
...
F("meat product", null);

The developer of this odd code is apparently attempting to make optional parameters on both ends; the intention is that both spam and jam are optional, but ham is always required.

Which overload does the compiler pick, and why? See if you can figure it out for yourself.

.

.

.

.

.

.

.

.

.

.

.

.

.

The second overload is chosen. (Were you surprised?)

The reason why is straightforward. The overload resolution algorithm has two candidates and must pick which is the best one on the basis of the conversions from the arguments to the formal parameter types. Look at the first argument. Either it is converted to string, or to... string. Hmph. We can conclude nothing from this argument, so we ignore it.

Now consider the second argument. We either convert null to object, if we pick the first overload, or to string, if we pick the second overload. String is obviously more specific than object; every string is an object, but not every object is a string. We strive to pick the overload that is more specific, so we choose the second overload, and call

F("meat product", null, null);

The moral of the story is, as I have said so many times, if it hurts when you do that then don't do that! Don't try to put an optional argument on both the front and back ends; it's just confusing as all heck, particularly if the types collide as they do here. Write the code as a single method with each optional parameter declaration coming at the end:

static void F(string ham, string spam = null, object jam = null) { }

With only one overload it is clear what argument corresponds to what parameter.

Now perhaps you see why we were so reticent to add optional arguments to overload resolution for ten years; it makes the algorithm less intuitive in some cases.

 

  • Oh god, how I hate the optional parameters in VB6, you cannot guess it. Another 10 years would have been better.

  • Is your example wrong? Shouldn't it be:

    static void F(string spam = null, string ham, object jam = null) { }

  • @Chris: That would be syntactically incorrect, as optional arguments cannot come before any non-optional arguments.

  • Look, whatever reasons you had for keeping optional parameters out of C# for 10 years, the bottom line is that optional parameters are 10 years overdue. They're just too important and the amount of function wrappers I had to write far outweigh any reasons you guys had for leaving them out.

  • On the other hand: if you kept them out so long, why did you guys let them in in c#4.0 then? #genuinelycurious

    There are legacy object models out there (I'm thinking specifically of the Office object model, but there are of course others as well) that were designed for use with VB 6 and earlier. Since VB has always supported optional arguments, many of those object models were difficult to use from C#. We have failed to move the mountain; those object models have not been rewritten to conform to the C# API design model in the last ten years. If the mountain won't come to you, you have to go to the mountain; enough customers told us that it was a major pain point to work with these important object models that we finally gritted our teeth and added the feature. - Eric

  • Who cares if it makes overload resolution less obvious?

    The people who design, implement, test and maintain the language care. The people who have to design and implement and test new language features on top of an already-too-complicated language semantics care. Customers whose programs are broken by the breaking changes introduced by this feature care. (The feature does introduce breaking changes; the example I give in this article was motivated by a question from a customer whose C# 3 code broke when they ported it to C# 4.) Customers whose programs do not work correctly because overload resolution silently chose an unexpected method care. Lots of people care. - Eric

    Optional parameters on constructors are critical to writing succinct immutable code. Imagine you have a Person class with dozens of different properties. Since you can write:

    new Person { FirstName = "Eric", LastName = "Lippert", HomeTown = "Waterloo" }

    ony when Person is mutable, the old way was to have many different constructor overloads, something messy like:

    new ImmutablePerson().WithFirstName("Eric").WithLastName("Lippert").WithHomeTown("Waterloo")

    which creates 3 extra Person objects that you don't need, or a single constructor that takes all possible parameters like:

    new ImmutablePerson(null, null, null, "Lippert", null, null, "Eric", null, null, null, null, null, "Waterloo", null, null, null, ...)

    Using optional parameters lets you write:

    new ImmutablePerson(FirstName: "Eric", LastName: "Lippert", HomeTown: "Waterloo")

    I agree. In C# 3 we swung the pendulum both ways: we encouraged a more immutable style of programming with monadic query comprehensions and immutable expression trees, but also encouraged a more mutable style of programming with object and collection initializers. We are thinking hard about ways to swing that pendulum more towards the immutable side of things; we don't want to make it more difficult to program in an immutable style. However, this factor was of only secondary importance as far as the optional arguments feature was concerned. In order to address the needs of people who want to program in an immutable style, we really should be considering features that directly address that concern, rather than merely doing incremental improvements around the periphery, like this feature. - Eric

  • @Gabe

    Or: new ImmutablePerson(new Person { etc } );

    Still messy of course, but it doesn't have to be as bad as your example.

  • Optional parameters are also invaluable when adding extra parameters to an existing method. In a crunch, you can add an extra optional parameter without having to update all the existing code for the new parameter. Sure, it is cutting a corner, but adding the default value to all the old calls doesn't actually add any business value to your program.

    I can't believe C# has been missing optional parameters for 10 years. It kills C programmers to admit anything about VB is good, doesn't it? :-)

  • @S: But before, it was still possible to define a new method with the additional argument, and have the old method call the new one.

  • @Jamie550 - I suppose so. Whether it's better to have optional parameters, or a russian-doll-like collection of overloads is a style preference.

    An optional parameter is less code and less typing than an overload. Since we are talking about cutting corners, I suppose that's relevant. YMMV. <shrug>

    With multiple optional parameters you can omit parameters in the middle, which I don't think you can do with an overload.

    e.g.

     Sub junk(ByVal param1 As String, Optional ByVal param2 As String = "foo", Optional ByVal param3 As String = "bar")

    ...

     Call junk("one", , "three")

    Without optional parameters, you'd need to use a magic number for the missing middle parameter. e.g. if by convention you use Nothing (null), it might be odd that you hand Nothing as param2, but when you retrieve the .Param2 property you get the non-Nothing default value.

  • >it makes the algorithm less intuitive in some cases.

    Imho, best solution is throw compile error in such cases.

  • I started my programming carreer using Delphi, which had optional parameters and I thought it was great.

    When I made the move to c# I really missed them and I thought that the overload alternative was just crazy (and it still is).

    Then a funny thing happened, I read "Code Complete 2 " (shameless plug: http://cc2e.com/ ) which taught all about writing readable code and making it as explicit as possilbe (remove all doubt).

    Is the version with ten parameters actually the same as the version with only 5 and does this warrant both functions having the same name? Most often, no.

    So the 'missing' optional paramters and reading a good book changed the way I write my code.

    Now, many years, and countless lines of code, later the optional parameters are back, and I fear them.

    Of course someone will read this and disagree and he (or she) will come up with a perfectly legitimate reason for using an optional parameter in a function. And I will predict that it that this person is smarter than 85% of all the programmers and will make a few valid points. But that is the problem of this (great) blog, (going by the comments) it is read by many very smart people that know what they are talking about and how to actually make great use of the programming language and carefully consider their options.

    Let gifted programmers just slap a few lines of code together, and when they wish to add functionality to a function that is in heavy use by the codebase, instead of doing a refactor, they will just slap another parameter on it (without much consideration), because it it compiles, it must be good.

    I guessed the function picked by the compiler wrong, I would have gone for the first function because that signature matches the parameters best. In numbers of parameters.

    How can the calling of a function with three parameters, of which one is NOT provided, be more specific than the version with two parameters which is an exact match based on the numer of parameters given?

  • >How can the calling of a function with three parameters, of which one is NOT provided, be more specific than the version with two parameters which is an exact match based on the numer of parameters given?

    Because string is more specific than an object? So, if we swap the types of the second parameters, as fallows:

    static void F(string ham, string jam = null) { }

    static void F(string spam, object ham, object jam = null) { }

    then the compiler will pick the first overload? Lately I've been thinking of migrating to C#, but have near zero experience and am not sure at all if I got this right...

  • *grumble*grumble* So, it turns out that if I have Javascript disabled, MSDN blogs eat my comments. Can this be fixed, please?

    @S: It kills C programmers to admit anything about VB is good, doesn't it? :-)

    Nice rhetoric. Now go do your research. To wit: Optional parameters have been in C++ since at least 1983. VB first appeared on the scene in 1991.

    As for the original topic: I'm not convinced default parameters have anything to do with this. Take the equivalent explicit overloads.

    static void F(string ham) { }

    static void F(string ham, object jam) { }

    static void F(string spam, string ham) { }

    static void F(string spam, string ham, object jam) { }

    The spam/ham version is still called, not the ham/jam. I don't see that this is any more obvious. And, even worse, the default implementation «static void F(string ham) { F(ham, null); }» is very wrong, while the with-default-parameters version doesn't allow this: «static void F(string ham, object jam = null) { F(null, ham, jam); }» is the only wrapper required, and it's much harder to mis-code.

  • Overload resolution when combined with default valued parameters was a difficult to grasp concept in C++ too. Add Koenig lookup and you could lose your sleep trying to figure out what was going on.

    In 3.0, second F gets called

    void F( string a , object b )

    {

    }

    void F( string a , string b )

    {

    }

    So another (perhaps imprecise) way to see why it does what is does is: algorithm picks up the best version using all provided values ignoring any optional parameters that may follow.

    Incidently, what happens when I do this? (VS 2010 will take a long time to fire up, not goint to do that.)

    static void F(string ham, object jam = null) { }

    static void F(string spam, string ham, object jam = null) { }

    static void F(string spam, string ham, object jam = null, object bread = null) { }

    ...

    F("meat product", null);

    Should be ambiguous match I guess.

Page 1 of 2 (23 items) 12