Optional argument corner cases, part three

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

A lot of people seem to think that this:

void M(string x, bool y = false) { ... whatever ... }

is actually a syntactic sugar for the way you used to have to write this in C#, which is:

void M(string x) { M(x, false); }
void M(string x, bool y) { ... whatever ... }

But it is not. The syntactic sugar here is not on the declaration side, but rather on the call side. There is only one method; when you make a call that omits optional arguments, the compiler simply inserts the arguments at the call site. That is:

M("hello");

is rewritten as

M("hello", false);

It would actually be pretty weird if we did it on the declaration side, because what would you do about this?

void N(bool a1 = false, bool a2 = false) { ... whatever ... }

Clearly we can't generate

void N() { N(false, false); }
void N(bool a1) { N(a1, false); }
void N(bool a2) { N(false, a2); }
void N(bool a1, bool a2) { ... whatever ... }

because now we have two methods with the same signature. But why would we need to generate the "a2" method in the first place? Because we added named arguments as well as optional arguments. Someone could call:

N(a2: true);

Clearly we have to rewrite the caller side, not the called side.

An argument with a default value does not change the signature at all, so anything that relies upon signature matching still needs to have an exact match. That is, even though you can say:

M("hello");

and you can say

Action<string> action = (string s)=>{M(s);};

You cannot say

Action<string> action = M;

Because M does not have a signature match with that delegate type; the delegate is expecting a method that takes a string, but you're giving it a method that takes a string and a bool. Where is the code generated that fills in the bool? There is no call site here, and the default value has to be filled in at the call site.

Similarly, you can't do something like this:

class B
{
public virtual void M(string x, bool y = false) {}
}
class D : B
{
public override void M(string x) {}
}

Or this:

class D : B
{
public override void M(string x, bool y = false, int z = 123) {}
}

The signatures have to match to do an override, and default values for missing arguments are not part of the signature.

Next time: more consequences of call-site rewriting.

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

Comments

  • Anonymous
    May 16, 2011
    Good information as always Eric. I think you've got a trivial typo on one of the examples though:  void N(bool a2) { N(false, a1); } Shouldn't that be:  void N(bool a2) { N(false, a2); } Handy that you posted this 3rd section just as I was reading the other two.

  • Anonymous
    May 16, 2011
    The comment has been removed

  • Anonymous
    May 16, 2011
    @Jon: The "formal parameters" have always had names.  Now the "actual parameters" do too. On the topic of names for arguments, there's: e.g the ad hominem argument ;)

  • Anonymous
    May 16, 2011
    Good info indead. This realy helps in understanding what is going on under the cover. Off topic: I would find it helpful if you would use syntax highlighting (there are some great plugins for that in Windows Live Writer and you are using Windows Live Writer aren't you? :-p)

  • Anonymous
    May 19, 2011
    Are named arguments the only reason why the compiler couldn't do it on the declaration side?

  • Anonymous
    October 05, 2011
    Jon: well, Visual Basic already had optional arguments and IIRC uses an attribute to represent the default value, rather than creating wrapper methods. Therefore it would be best for compatibility if C# takes the same approach. In addition, only having a single method defined is potentially better for performance (the number of function calls at runtime is potentially reduced.)