19

I came across this today, and I am surprised that I haven't noticed it before. Given a simple C# program similar to the following:

public class Program
{
    public static void Main(string[] args)
    {
        Method(); // Called the method with no arguments.
        Method("a string"); // Called the method with a string.

        Console.ReadLine();
    }

    public static void Method()
    {
        Console.WriteLine("Called the method with no arguments.");
    }

    public static void Method(string aString = "a string")
    {
        Console.WriteLine("Called the method with a string.");
    }
}

You get the output shown in the comments for each method call.

I understand why the compiler chooses the overloads that it does, but why is this allowed in the first place? I am not asking what the overload resolution rules are, I understand those, but I am asking if there is a technical reason why the compiler allows what are essentially two overloads with the same signature?

As far as I can tell, a function overload with a signature that differs from another overload only through having an additional optional argument offers nothing more than it would if the argument (and all preceding arguments) were simply required.

One thing it does do is makes it possible for a programmer (who probably isn't paying enough attention) to think they're calling a different overload to the one that they actually are.

I suppose it's a fairly uncommon case, and the answer for why this is allowed may just be because it's simply not worth the complexity to disallow it, but is there another reason why C# allows function overloads to differ from others solely through having one additional optional argument?

Adam Goodwin
  • 3,951
  • 5
  • 28
  • 33
  • 3
    The C# team held this off as long as they could. But they caved in at version 4 to popular demand, Office programming in particular was way too nasty without it. Just set your own coding standard to avoid the feature. – Hans Passant Jun 18 '16 at 08:58
  • 1
    Possible duplicate of [Conflicting overloaded methods with optional parameters](http://stackoverflow.com/questions/2674417/conflicting-overloaded-methods-with-optional-parameters) – Joe Jun 18 '16 at 12:51

3 Answers3

14

His point that Eric Lippert could have an answer lead me to this https://meta.stackoverflow.com/a/323382/1880663, which makes it sounds like my question will only annoy him. I'll try to rephrase it to make it clearer that I'm asking about the language design, and that I'm not looking for a spec reference

I appreciate it! I am happy to talk about language design; what annoys me is when I waste time doing so when the questioner is very unclear about what would actually satisfy their request. I think your question was phrased clearly.


The comment to your question posted by Hans is correct. The language design team was well aware of the issue you raise, and this is far from the only potential ambiguity created by optional / named arguments. We considered a great many scenarios for a long time and designed the feature as carefully as possible to mitigate potential problems.

All design processes are the result of compromise between competing design principles. Obviously there were many arguments for the feature that had to be balanced against the significant design, implementation and testing costs, as well as the costs to users in the form of confusion, bugs, and so on, from accidental construction of ambiguities such as the one you point out.

I'm not going to rehash what was dozens of hours of debate; let me just give you the high points.

The primary motivating scenario for the feature was, as Hans notes, popular demand, particularly coming from developers who use C# with Office. (And full disclosure, as a guy on the team that wrote the C# programming model for Word and Excel before I joined the C# team, I was literally the first one asking for it; the irony that I then had to implement this difficult feature a couple years later was not lost on me.) Office object models were designed to be used from Visual Basic, a language that has long had optional / named parameter support.

C# 4 might have seemed like a bit of a "thin" release in terms of obvious features. That's because a lot of the work done in that release was infrastructure for allowing more seamless interoperability with object models that were designed for dynamic languages. The dynamic typing feature is the obvious one, but there were numerous other small features added that combine together to make working with dynamic and legacy COM object models easier. Named / optional arguments was just one of them.

The fact that we had existing languages like VB that had this specific feature for decades and the world hadn't ended yet was further evidence that the feature was both doable and valuable. It's great having an example where you can learn from its successes and failures before designing a new version of the feature.

As for the specific situation you mention: we considered doing things like detecting when there was a possible ambiguity and making a warning, but that then opens up a whole other cans of worms. Warnings have to be for code that is common, plausible and almost certainly wrong, and there should be a clear way to address the problem that causes the warning to go away. Writing an ambiguity detector is a lot of work; believe me, it took way longer to write the ambiguity detection in overload resolution than it took to write the code to handle successful cases. We didn't want to spend a lot of time on adding a warning for a rare scenario that is hard to detect and that there might be no clear advice on how to eliminate the warning.

Also, frankly, if you write code where you have two methods named the same thing that do something completely different depending on which one you call, you already have a larger design problem on your hands! Fix that problem first, rather than worrying that someone is going to accidentally call the wrong method; make it so that either method is the right one to call.

Community
  • 1
  • 1
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    Well that definitely answers it, thanks. As for your last paragraph, after I stumbled across this I quickly saw that using default arguments wasn't the best for me - although not because the two functions had completely different implementations. I had two functions doing 95% the same thing, but with different parameters, so I wanted to combine the implementation privately and keep the two separate public signatures. I would have used default arguments for the private method, but that would have forced one of the public methods to recursively call itself due to the overload resolution rules. – Adam Goodwin Jun 18 '16 at 15:37
  • I ended up making all of the parameters required for the private method, passing null for the unused ones. It still doesn't feel right but I'll sleep on it... – Adam Goodwin Jun 18 '16 at 15:40
10

This behaviour is specified by Microsoft at the MSDN. Have a look at Named and Optional Arguments (C# Programming Guide).

If two candidates are judged to be equally good, preference goes to a candidate that does not have optional parameters for which arguments were omitted in the call. This is a consequence of a general preference in overload resolution for candidates that have fewer parameters.

A reason why they decided to implement it the way like this could be if you want to overload a method afterwards. So you don't have to change all your method calls that are already written.

UPDATE

I'm surprised, also Jon Skeet has no real explantation why they did it like this.

Community
  • 1
  • 1
Fruchtzwerg
  • 10,999
  • 12
  • 40
  • 49
  • I have seen that page, and like I said I understand why the overloads are chosen in the way that they are, but I'm still wondering about why the rules around optional arguments allow the ambiguity in the first place. Maybe it's not a good question for Stack Overflow, as I'm essentially asking why a language design decision was made. – Adam Goodwin Jun 18 '16 at 07:45
  • Your update with John Skeet's comment is interesting, I didn't find that before asking my question but it does directly address my question. His point that Eric Lippert could have an answer lead me to this http://meta.stackoverflow.com/a/323382/1880663, which makes it sounds like my question will only annoy him. I'll try to rephrase it to make it clearer that I'm asking about the language design, and that I'm not looking for a spec reference. – Adam Goodwin Jun 18 '16 at 08:24
3

I think this question basically boils down to how those signatures are represented by the intermediate language. Note that the signatures of both overloads are not equal! The second method has a signature like this:

.method public hidebysig static void Method([opt] string aString) cil managed
{
    .param [1] = string('a string')
    // ...
}

In IL the signature of the method is different. It takes a string, which is marked as optional. This changes the behaviour of how the parameter get's initialize, but does not change the presence of this parameter.

The compiler is not able to decide, which method you are calling, so it uses the one that fits best, based on the parameters you provide. Since you did not provide any parameters for the first call, it assumes that you are calling the overload without any parameters.

In the end it is a question about good code design. As a rule of thumb, I either use optional parameters or overloads, depending on what I want to do: Optional parameters are good, if the logic within the method does not depend on the provided arguments, while overloads are good to provide a different implementation for different sets of arguments. If you ever find yourself checking if a parameter equals a default value in order to decide what to do, you should probably go for an overload. On the other hand, if you find yourself repeating large chunks of code in many overloads, you should try extracting optional parameters.

There's also a good answer of Chuck Skeet to this question.

Community
  • 1
  • 1
Carsten
  • 11,287
  • 7
  • 39
  • 62
  • I think what you've explained about how it compiles to CIL is a good point. If the method with the optional argument was compiled into two methods (one with the argument and one without), then I would definitely expect a compiler error because you'd end up with two overloads with the same signature. But as you've explained it, that's not what happens, so although you can't actually call the method with the optional argument _without_ providing that optional argument (unless through reflection) there is no conflict in the CIL and so I suppose no fundamental reason to disallow my example. – Adam Goodwin Jun 18 '16 at 11:31