Named and Optional arguments – ties and philosophies

Okay, my attempt at a clever title failed… Ties and Philosophers? I oughtta stick with technical writing. :)

We’re almost done with our chat about named and optional arguments. We’ve covered what the feature is about, and covered overload resolution in more detail. This time I want to do a quick wrap up of our discussion by talking about the tie breaker rules, and then I want to give a bit of background and philosophy behind why we’re electing to do this feature now, instead of several releases ago when it was first considered.

Tie breakers

During overload resolution, the compiler may find that there are two or more candidates that are perfectly legal candidates given the arguments specified. When that happens, we apply our tie breaker rules to figure out which of the methods is the best one.

In the past, breaking ties was simple (well, not really, but simpler than it will be now!). Because every candidate had the same number of arguments, (again, not really – param arrays! I’ll explain that in a sec) we simply needed to apply our betterness rules to the conversions to see if we could find a best method. This check amounted to taking each pair of candidates, and checking if either of them has equal-or-better conversions for each parameter. If so, that method was considered the better choice, and was chosen as the best method to bind to. Optional arguments throw a wrench in the mix – now we can have two candidates that are both perfectly valid, but have a different number of parameters.

First, a quick example:

 class C
{
    public void Foo(object o) { }
    public void Foo(int x) { }

    static void Main()
    {
        C c = new C();
        c.Foo(10);
    }
}

In this example, both overloads of Foo are applicable – the integer 10 is convertible to object, and is certainly convertible to int. The traditional tie breaker rule simply says that since 10 is an integer, the conversion to int is better than the conversion to object. Therefore Foo(int x) wins.

Param arrays

Well what about parameter arrays? Lets tweak our example a little bit:

 class C
{
    public void Foo(int x, int y) { }
    public void Foo(params int[] x) { }

    static void Main()
    {
        C c = new C();
        c.Foo(10, 20);
    }
}

In this example, the compiler takes the params array, and expands it so that the signature looks like Foo(int x_1, int x_2), and uses that signature as the candidate signature. However, it also notes that the signature came from a params array, and param arrays are treated as second-class compared to real parameters. Since everything else is equal, the second-class-ness of the params array loses the tie to the first class parameters.

Optional arguments

So how do optionals play into the mix? Well lets consider this third example:

 class C
{
    public void Foo(int x) { }
    public void Foo(int x, int y = 0, int z = 10) { }

    static void Main()
    {
        C c = new C();
        c.Foo(10);
    }
}

In this example, both candidates are applicable, since they both match for the first argument, and the second candidate has optional arguments for the rest. The conversion for the first argument is the same for both candidates, and there are no param arrays. How do we pick which one is better?

Much like param arrays, the thing to do here is to treat optional arguments like param arrays – as second class arguments. Because the first method has no optional arguments and the second one does, we pick the first one.

Notice that if both candidates had optional arguments, at that point we’ve got nothing more to go on, and so the call is ambiguous.

Philosophy time!

Okay! Time for some philosophy. Why are we doing this feature now? Why didn’t we do it earlier?

Let me tackle the second question first, as it’s got a shorter answer. The reason we didn’t do it earlier is that we really didn’t want this feature in our language. We’ve pushed back on it for this long because it’s not the paradigm that we want.

So that brings us to the first question – why now?

Well, the quick answer is because of COM. It just won’t go away! Try as we might, people still use it (and are still going to continue using it). What does COM have to do with C#? Office. Office PIAs. The Office PIAs are designed such that many of the methods have about 30 parameters, and all of them are optional. Most of the time, what you’d want to do is specify one argument, and use the defaults for the rest.

Enter named and optional arguments. Because we allow you now to call methods without specifying their optional arguments, you can now call these Office methods without passing Type.Missing in as every other argument. And because we allow you to use names to specify exactly which parameter you’re passing this argument for, you can call the method by only passing what you want, and omitting the rest.

Combine this with our COM-no-ref feature in which the compiler takes the optional ref parameter and generates a local temporary variable for you and passes that as the parameter, your COM code will look much cleaner and will be much less tedious to write.

As I mentioned way back when, one of the big themes for C# 4.0 is interop with other “runtimes” – COM, dynamic languages, other user-defined type systems. That theme made this feature set a must-have.

As always, I would love all the feedback you guys have! And of course, happy coding!

kick it on DotNetKicks.com