Optional argument corner cases, part one

(This is part one of a series on the corner cases of optional arguments in C# 4. Part two is here.)

In C# 4.0 we added "optional arguments"; that is, you can state in the declaration of a method's parameter that if certain arguments are omitted, then constants can be substituted for them:

void M(int x = 123, int y = 456) { }

can be called as M(), M(0) and M(0, 1). The first two cases are treated as though you'd said M(123, 456) and M(0, 456) respectively.

This was a controversial feature for the design team, which had resisted adding this feature for almost ten years despite numerous requests for it and the example of similar features in languages like C++ and Visual Basic. Though obviously convenient, the convenience comes at a pretty high price of bizarre corner cases that the compiler team definitely needs to deal with, and customers occasionally run into by accident. I thought I might talk a bit about a few of those bizarre corner cases.

First off, a couple having to do with interfaces. Let's take a look at how optional arguments interact with "implicit" interface implementations.

interface IFoo
{
void M(int x = 123);
}
class Foo : IFoo
{
public void M(int x){}
}

Here the method M is implicitly implemented by Foo's method M. What happens here?

Foo foo = new Foo();
foo.M();

An overload resolution error, that's what. Just because Foo's declaration of M happens to implicitly be the implementation of IFoo.M does not mean that the overload resolution algorithm figures that out and takes advantage of it. In fact, things could be quite confusing if it did; suppose we had:

interface IFoo
{
void M(int x = 123);
}
interface IBar
{
void M(int x = 456);
}
class Foo : IFoo, IBar
{
public void M(int x){}
}

Foo's method M implements both interface methods implicitly; which default parameter value should it pay attention to?

The rule here is straightforward: when considering a method on a class, the optional arguments of the class's implementation are the only ones that are considered to be "in effect" during overload resolution. The optional parameters on the interface implementation only come into effect when calling via the interface:

IFoo foo = new Foo();
foo.M(); // Legal

This also explains why the compiler gives you a stern warning when you do an explicit interface implementation:

interface IAbc
{
void M(int x) {}
}
class Abc : IAbc
{
void IAbc.M(int x = 123) {}
}

The compiler warns you that the default value on the parameter is meaningless. The only way to call the method is via the interface, and how does the compiler know when resolving the call to the interface method what implementation to look up the default argument values on? The whole point of interfaces is to decouple the method from the implementation, so there's no way to know!

Next time: some more thoughts on inconsistencies between class and interface optional arguments.

(This is part one of a series on the corner cases of optional arguments in C# 4. Part two is here.)

Comments

  • Anonymous
    May 09, 2011
    Seeing that interfaces can't have any code, I'm trying to understand how allowing optional arguments for an interface method would be helpful.  Couldn't you simply forbid optional arguments in interfaces altogether?

  • Anonymous
    May 09, 2011
    The comment has been removed

  • Anonymous
    May 09, 2011
    The comment has been removed

  • Anonymous
    May 09, 2011
    @ErikF: allowing optional arguments on interfaces lets users of those interfaces omit them when calling methods via interface-typed references. Not allowing optional arguments on interfaces would also defeat a very major use case for them, which is COM interop  (because methods with 10+ arguments, only 1 or 2 of which are required, is fairly common there - if you ever saw Office APIs, you know what I mean).

  • Anonymous
    May 09, 2011
    The comment has been removed

  • Anonymous
    May 09, 2011
    The comment has been removed

  • Anonymous
    May 09, 2011
    How did the VB compiler handle these cases?

  • Anonymous
    May 09, 2011
    @Brett: VB requires that classes repeat exactly any optional/defaulted arguments from the interfaces they implement - it's considered part of the signature of the method. If you omit the default value on your implementation, the compiler will complain that "there is no matching sub/function on the interface" The obvious question, then, is why C# couldn't do the same - i.e. refuse to consider method an implementation if values of optional arguments don't match the interface, and then ultimately complain that class didn't properly implement an interface it listed because no matching implementing method was found. My tentative guess is that this would have broken back-compat for cases where existing C# code implements interfaces defined in VB with optional arguments - since any pre-4.0 C# code wouldn't have used default values in such cases, and hence be broken by 4.0.

  • Anonymous
    May 09, 2011
    The compiler would also give you an error about using the "public" modifier in your interface!

  • Anonymous
    May 09, 2011
    The comment has been removed