Optional argument corner cases, part one

Optional argument corner cases, part one

Rate This
  • Comments 10

(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.)

  • 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?

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

    You probably get asked this way too often, but What is the justification for allowing that code to compile with a warning rather than with an error?

    Thanks.

  • @Brian

    I would gues its the same as having unreachable code.

    There is no error per se. The explicit implementation of the interface means that the methods the interface defines can only be called through the interface itself and therefore the optional parameters defined in the class impementing the interface are "unreachable".

  • @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).

  • Initially I would have wanted this...but seeing it in this use case, I'm sorry but I wish they had stuck to their guns and not tried to make c# more "like VB".

  • @InBetween: True, but in the case of unreachable code there is a practical situation where a programmer might temporarily want the code unreachable but would still prefer a warning.  In particular, this applies in the case where the coder is stuffing a return in the middle of a function for quick and dirty debugging purposes.  In contrast to unreachable code, I cannot think of a case where a programmer would want to leave such a useless piece of code in place.  It might also apply in the case that a programmer is using Macros...though usually the macros would be used to kill the unreachable code, too.

  • How did the VB compiler handle these cases?

  • @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.

  • The compiler would also give you an error about using the "public" modifier in your interface!

  • I'm a bit confused by the warning the compiler gives you if both the interface and an explicit implementation give a default value for a parameter:

    "The default value specified for parameter 'x' will have no effect because it applies to a member that is used in contexts that do not allow optional arguments"

    Surely optional arguments *are* allowed in this case, it's just that the one provided on the explicit implementation won't be used?

Page 1 of 1 (10 items)