80

Seems like in .NET Framework there is an issue with optional parameters when you override the method. The output of the code below is: "bbb" "aaa" . But the output I'm expecting is: "bbb" "bbb" .Is there a solution for this. I know it can be solved with method overloading but wondering the reason for this. Also the code works fine in Mono.

class Program
{
    class AAA
    {
        public virtual void MyMethod(string s = "aaa")
        {
            Console.WriteLine(s);
        }

        public virtual void MyMethod2()
        {
            MyMethod();
        }
    }

    class BBB : AAA
    {
        public override void MyMethod(string s = "bbb")
        {
            base.MyMethod(s);
        }

        public override void MyMethod2()
        {
            MyMethod();
        }
    }

    static void Main(string[] args)
    {
        BBB asd = new BBB();
        asd.MyMethod();
        asd.MyMethod2();
    }
}
Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
SARI
  • 854
  • 1
  • 8
  • 12
  • 6
    Optional parameters are bad, mmkay! TBH, I have never even bothered using them. – leppie Jan 18 '12 at 12:10
  • 46
    @leppie, It's very usfull feature. Reject a feature in all scenarios isn't wise. – gdoron Jan 18 '12 at 12:25
  • 1
    I m curious to know if that happens in VB.Net also and is not specific to C# compiler..will need to check.. – S2S2 Jan 18 '12 at 12:33
  • 2
    That sounds like a bug in Mono. – Jon Hanna Jan 18 '12 at 12:52
  • 2
    @Jon how so? here we have an example where used *outside* the class, the **overridden** version is inspected for the default, and *inside* the class the **base** version is inspected for the default; that is not consistent – Marc Gravell Jan 18 '12 at 12:56
  • @MarcGravell Actually, on consideration, both behaviours seem reasonable. – Jon Hanna Jan 18 '12 at 13:04
  • @sapsari did you see any compiler warnings while above code was compiled? – S2S2 Jan 18 '12 at 13:12
  • @Vijay Using C# 4.0 (from Microsoft, Warning level 4 enabled), I didn't. – Christian.K Jan 18 '12 at 13:15
  • @gdoron: I am sure there are good reasons to use it. But I am yet to find such a case. Normally (read ideally) you want your defaults to be of any type and not just a very limited subset of types (aka primitives and some others). PS: I did not say 'I am rejecting the feature in all scenarios', not sure how you came to that conclusion. – leppie Jan 18 '12 at 13:22
  • @sapsari: Please test this with both default values being the same. I suspect a signature mismatch. Which would wholeheartedly suck.... – leppie Jan 18 '12 at 14:07
  • See also http://stackoverflow.com/questions/241134/what-is-the-worst-gotcha-in-c-sharp-or-net/2837357#2837357 – BlueRaja - Danny Pflughoeft Jan 18 '12 at 17:02
  • 6
    "I did not say 'I am rejecting the feature in all scenarios'" -- of course you did. "not sure how you came to that conclusion" -- uh, "Optional parameters are bad, mmkay!" – Jim Balter Nov 08 '13 at 18:01
  • Hm... but the above code returns "bbb" "bbb" for me... – Petar Ivanov Nov 20 '21 at 00:30

9 Answers9

39

You can disambiguate by calling:

this.MyMethod();

(in MyMethod2())

Whether it is a bug is tricky; it does look inconsistent, though. ReSharper warns you simply not to have changes to the default value in an override, if that helps ;p Of course, ReSharper also tells you the this. is redundant, and offers to remove it for you ... which changes the behaviour - so ReSharper also isn't perfect.

It does look like it could qualify as a compiler bug, I'll grant you. I'd need to look really carefully to be sure... where's Eric when you need him, eh?


Edit:

The key point here is the language spec; let's look at §7.5.3:

For example, the set of candidates for a method invocation does not include methods marked override (§7.4), and methods in a base class are not candidates if any method in a derived class is applicable (§7.6.5.1).

(and indeed §7.4 clearly omits override methods from consideration)

There's some conflict here.... it states that the base methods are not used if there is an applicable method in a derived class - which would lead us to the derived method, but at the same time, it says that methods marked override are not considered.

But, §7.5.1.1 then states:

For virtual methods and indexers defined in classes, the parameter list is picked from the most specific declaration or override of the function member, starting with the static type of the receiver, and searching through its base classes.

and then §7.5.1.2 explains how the values are evaluated at the time of the invoke:

During the run-time processing of a function member invocation (§7.5.4), the expressions or variable references of an argument list are evaluated in order, from left to right, as follows:

...(snip)...

When arguments are omitted from a function member with corresponding optional parameters, the default arguments of the function member declaration are implicitly passed. Because these are always constant, their evaluation will not impact the evaluation order of the remaining arguments.

This explicitly highlights that it is looking at the argument list, which was previously defined in §7.5.1.1 as coming from the most specific declaration or override. It seems reasonable that this is the "method declaration" that is referred to in §7.5.1.2, thus the value passed should be from the most derived up-to the static type.

This would suggest: csc has a bug, and it should be using the derived version ("bbb bbb") unless it is restricted (via base., or casting to a base-type) to looking at the base method declarations (§7.6.8).

Pang
  • 9,564
  • 146
  • 81
  • 122
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • BTW, does this behaviour only expose itself in the presence of optional parameters? – leppie Jan 18 '12 at 13:18
  • @leppie, yes since it's purely about the handling of optional parameters on compilation, it only exposes itself in the presence of optional parameters. If you believe it's a bug can you find somewhere in the specification that says this shouldn't happen? I can't find anything either way on it. I'll edit my answer to add why this seems the more intuitive behaviour to me. – Jon Hanna Jan 18 '12 at 14:03
  • @JonHanna: I suspect the runtime fails to match the method signatures due to having a different compile-time constant for the optional parameter. Sound reasonable? – leppie Jan 18 '12 at 14:06
  • 1
    @leppie Runtime always calls the derived, that part is clearly correct. The debatable point is over what it should call it with. – Jon Hanna Jan 18 '12 at 14:17
  • @leppie - added a spec reference; thoughts? – Marc Gravell Jan 18 '12 at 14:26
  • That's why I follow StyleCop's rule to always use `this.` and change ReSharper's default setting. – Agent_9191 Jan 18 '12 at 14:28
  • The spec you reference is for **overload resolution** – Fernando Jan 18 '12 at 14:33
  • @Fernando which is relevant; the first thing it has to to is resolve; but see my update with the related 7.5.1.1, which defines the parameter list – Marc Gravell Jan 18 '12 at 14:34
  • Your edit is interesting. When I played with this upon 4.0 coming out I expected "aaa aaa"; well, "base base" my playing code was different ;) I'm not sure the "bbb" is wrong though, since all three behaviours (csc, mono, and that you propose) fulfil the letter of this in overload resolution and then sprinkle sugar at the calling site differently, upon which I can't find anything clear about conflicts (not like I fine-tooth-combed it). The above would be clearly relevant if BBB defined a parameterless `MyMethod()`, but the behaviour in that case does match the above. – Jon Hanna Jan 18 '12 at 14:38
  • The derived method is actually being correctly invoked. What is at issue is the value of the default parameter. – Fernando Jan 18 '12 at 14:49
  • 1
    @Fernando yes, we know; at no point has anyone suggested the wrong **method** was being invoked; everything is about how the compiler is choosing the parameter values to bake in for the optional values – Marc Gravell Jan 18 '12 at 14:50
  • @MarcGravell hmmm, ok. I just can't see where the specs that you reference have anything to do with the baked-in optional values. – Fernando Jan 18 '12 at 14:56
  • @Fernando 7.5.1.1 defines where the parameter list comes from; unless I am mistaken, that has **everything** to do with where the baked in values come from – Marc Gravell Jan 18 '12 at 15:00
  • @MarcGravell While I most definitely agree that this is a compiler bug, I don't think 7.5.1.1 has anything to do with the issue. I believe 7.5.1.1 is essentially about choosing the correct method to invoke. There is no text there which states anything about the default parameter relevant to this discussion. – Fernando Jan 18 '12 at 15:13
  • @Fernando how the values are evaluated is defined in 7.5.1.2; see update – Marc Gravell Jan 19 '12 at 07:21
28

One thing worth noting here, is that the overridden version is called each time. Change the override to:

public override void MyMethod(string s = "bbb")
{
  Console.Write("derived: ");
  base.MyMethod(s);
}

And the output is:

derived: bbb
derived: aaa

A method in a class can do one or two of the following:

  1. It defines an interface for other code to call.
  2. It defines an implementation to execute when called.

It may not do both, as an abstract method does only the former.

Within BBB the call MyMethod() calls a method defined in AAA.

Because there is an override in BBB, calling that method results in an implementation in BBB being called.

Now, the definition in AAA informs calling code of two things (well, a few others too that don't matter here).

  1. The signature void MyMethod(string).
  2. (For those languages that support it) the default value for the single parameter is "aaa" and therefore when compiling code of the form MyMethod() if no method matching MyMethod() can be found, you may replace it with a call to `MyMethod("aaa").

So, that's what the call in BBB does: The compiler sees a call to MyMethod(), doesn't find a method MyMethod() but does find a method MyMethod(string). It also sees that at the place where it is defined there's a default value of "aaa", so at compile time it changes this to a call to MyMethod("aaa").

From within BBB, AAA is considered the place where AAA's methods are defined, even if overridden in BBB, so that they can be over-ridden.

At run-time, MyMethod(string) is called with the argument "aaa". Because there is a overridden form, that is the form called, but it is not called with "bbb" because that value has nothing to do with the run-time implementation but with the compile-time definition.

Adding this. changes which definition is examined, and so changes what argument is used in the call.

Edit: Why this seems more intuitive to me.

Personally, and since I'm talking of what is intuitive it can only be personal, I find this more intuitive for the following reason:

If I was coding BBB then whether calling or overriding MyMethod(string), I'd think of that as "doing AAA stuff" - it's BBBs take on "doing AAA stuff", but it's doing AAA stuff all the same. Hence whether calling or overriding, I'm going to be aware of the fact that it was AAA that defined MyMethod(string).

If I was calling code that used BBB, I'd think of "using BBB stuff". I might not be very aware of which was originally defined in AAA, and I'd perhaps think of this as merely an implementation detail (if I didn't also use the AAA interface nearby).

The compiler's behaviour matches my intuition, which is why when first reading the question it seemed to me that Mono had a bug. Upon consideration, I can't see how either fulfils the specified behaviour better than the other.

For that matter though, while remaining at a personal level, I'd never use optional parameters with abstract, virtual or overridden methods, and if overriding someone else's that did, I'd match theirs.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • +1 Good observation. What happens when both defaults are the same? – leppie Jan 18 '12 at 14:09
  • 2
    @leppie When both defaults are the same, then the difference becomes insignificant because either has the same results and the syntactic sugar tastes just as sweet whatever you expect. I note that at least one tool out there has this as a recommendation. Personally, I'd stay away from defaults on virtual entirely. – Jon Hanna Jan 18 '12 at 14:15
  • I added an answer that references the spec... thoughts? – Marc Gravell Jan 18 '12 at 14:27
  • @JonHanna: You were suppose to change the implementation (eg ignore the parameter and print something static ;p). – leppie Jan 18 '12 at 14:35
  • 1
    I still think your findings pretty much shows this is a real bug. – leppie Jan 18 '12 at 14:40
  • @leppie Then what is that bug? What defines the expected behaviour, from which this departs. From what I can make out, "aaa bbb", "bbb bbb" and the "aaa aaa" Marc suggests in his answer would all match the letter of the spec. – Jon Hanna Jan 18 '12 at 14:45
  • @Jon contrary to where you say "but it is not called with "bbb" because that value has nothing to do with the run-time implementation but with the compile-time definition." - 7.5.1.1 says the opposite, and the most derived version known is inspected; as such, I am of the opinion (until shown a spec reference otherwise, and contrary to your comment 2 above) that only "bbb bbb" is valid – Marc Gravell Jan 18 '12 at 14:58
  • @MarcGravell reading that section again, I remain unconvinced that the spec says anything clear. I think it's underspecified. – Jon Hanna Jan 18 '12 at 15:15
  • 1
    Your thoughts on "this." changing the method that is resolved is ... not obvious. Please provide a spec reference to support this argument. It does not agree with the spec as stated in 7.5.1.1 and 7.5.1.2. – Marc Gravell Jan 19 '12 at 07:58
  • @MarcGravell I don't see anything clear there in favour of either way. All I can see there is that if there was a parameterless method of the same name in BBB it would be called, but failing that the method defined in AAA and overridden in BBB will be. I see nothing that convinces me either is correct in terms of what arguments are passed. Now, if I'm not missing something then that's a flaw in itself, because it's not like `MyMethod()` is such bizarre code that unspecified behaviour is justified. – Jon Hanna Jan 19 '12 at 09:29
17

This looks like a bug to me. I believe it is well specified, and that it should behave in the same way as if you call the method with the explicit this prefix.

I've simplified the example to only use a single virtual method, and show both which implementation is called and what the parameter value is:

using System;

class Base
{
    public virtual void M(string text = "base-default")
    {
        Console.WriteLine("Base.M: {0}", text);
    }   
}

class Derived : Base
{
    public override void M(string text = "derived-default")
    {
        Console.WriteLine("Derived.M: {0}", text);
    }

    public void RunTests()
    {
        M();      // Prints Derived.M: base-default
        this.M(); // Prints Derived.M: derived-default
        base.M(); // Prints Base.M: base-default
    }
}

class Test
{
    static void Main()
    {
        Derived d = new Derived();
        d.RunTests();
    }
}

So all we need to worry about are the three calls within RunTests. The important bits of the spec for the first two calls are section 7.5.1.1, which talks about the parameter list to be used when finding corresponding parameters:

For virtual methods and indexers defined in classes, the parameter list is picked from the most specific declaration or override of the function member, starting with the static type of the receiver, and searching through its base classes.

And section 7.5.1.2:

When arguments are omitted from a function member with corresponding optional parameters, the default arguments of the function member declaration are implicitly passed.

The "corresponding optional parameter" is the bit that ties 7.5.2 to 7.5.1.1.

For both M() and this.M(), that parameter list should be the one in Derived as static type of the receiver is Derived, Indeed, you can tell that the compiler treats that as the parameter list earlier in the compilation, as if you make the parameter mandatory in Derived.M(), both of the calls fail - so the M() call requires the parameter to have a default value in Derived, but then ignores it!

Indeed, it gets worse: if you provide a default value for the parameter in Derived but make it mandatory in Base, the call M() ends up using null as the argument value. If nothing else, I'd say that proves it's a bug: that null value can't come from anywhere valid. (It's null due to that being the default value of the string type; it always just uses the default value for the parameter type.)

Section 7.6.8 of the spec deals with base.M(), which says that as well as the non-virtual behaviour, the expression is considered as ((Base) this).M(); so it's entirely correct for the base method to be used to determine the effective parameter list. That means the final line is correct.

Just to make things easier for anyone who wants to see the really odd bug described above, where a value not specified anywhere is used:

using System;

class Base
{
    public virtual void M(int x)
    {
        // This isn't called
    }   
}

class Derived : Base
{
    public override void M(int x = 5)
    {
        Console.WriteLine("Derived.M: {0}", x);
    }

    public void RunTests()
    {
        M();      // Prints Derived.M: 0
    }

    static void Main()
    {
        new Derived().RunTests();
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Re "well specified" - can you look at the tiny edit I made to my answer re the wording in §7.5.1.2 - it does seem a little ambiguous...? – Marc Gravell Jan 19 '12 at 07:31
  • @MarcGravell: I don't think so - the part which talks about "missing" arguments specifically says that the value is taken from the *corresponding parameter*, which is the one specified in 7.5.1.1. So the function member is the base declaration, but its list of corresponding parameters for this invocation is specified in the override. At least, I think that's a reasonable way to read it. – Jon Skeet Jan 19 '12 at 07:42
  • ah yes; maybe it is correct to assume that "method declaration" is the declaration used for the argument list (7.5.1.1), rather than the declaration used for overload resolution (7.4). Either way, the behaviour of with/without `this.`, and the required/optional oddities you highlight: are all seriously odd and very-broken-looking. – Marc Gravell Jan 19 '12 at 07:52
  • The implications of this possible-bug are murky to me. I'd like to get your general recommendations for a "best practice" (for less-advanced developers) to avoid any potential pitfalls in this area. I've posted a question to that effect here: http://stackoverflow.com/questions/9381850/whats-the-best-practice-for-optional-parameters-on-overriden-methods. Would you be so kind as to post a reply to that question? – kmote Feb 21 '12 at 17:07
  • @kmote: I believe Eric Lippert already has a whole range of recommendations around optional parameters - see his blog. I can't say I've used them enough to discover all the relevant pitfalls. – Jon Skeet Feb 21 '12 at 17:14
10

Have you tried:

 public override void MyMethod2()
    {
        this.MyMethod();
    }

So you actually tell your program to use the overriden Method.

basti
  • 2,649
  • 3
  • 31
  • 46
10

The behaviour is definitely very strange; it is not clear to me if it is in fact a bug in the compiler, but it might be.

The campus got a fair amount of snow last night and Seattle is not very good about dealing with snow. My bus is not running this morning so I'm not going to be able to get into the office to compare what C# 4, C# 5 and Roslyn say about this case and if they disagree. I'll try to post an analysis later this week once I'm back in the office and can use proper debugging tools.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Excuses, excuses, excuses :p (just kidding) Looking forward to the results. – leppie Jan 18 '12 at 14:50
  • ... he said as he donned his snowboarding boots and headed to the mountain. – Fernando Jan 18 '12 at 15:07
  • very entertaining discussion on this question.. :) – S2S2 Jan 18 '12 at 15:13
  • 1
    What looks especially odd is the difference between this.MyMethod() and simply MyMethod(). Good luck with the snow. – Marc Gravell Jan 18 '12 at 17:15
  • I'm pretty sure it *is* a bug - or at least that there's *a* bug... see my answer, where I checked what happens when the base class method definition has a mandatory parameter but the derived class method definition as an optional parameter... a call to `M()` from the derived class then succeeds but uses `null` as the argument value! – Jon Skeet Jan 19 '12 at 07:27
  • 2
    Ping! Did you blog about this and I missed it? In any case, a link to your analysis from this answer would be very nice. – Ben Voigt Aug 07 '12 at 21:08
5

May be this is due to ambiguity and the compiler is giving priority to the base/super class. The below change to code of your class BBB with adding reference to this keyword, gives the output 'bbb bbb':

class BBB : AAA
{
    public override void MyMethod(string s = "bbb")
    {
        base.MyMethod(s);
    }

    public override void MyMethod2()
    {
        this.MyMethod(); //added this keyword here
    }
}

One of the things it implies is you should always use the this keyword whenever you are calling properties or methods on the current instance of class as a best practice.

I would be concerned if this ambiguity in base and child method didn't even raise a compiler warning (if not error), but if it does then that was unseen I suppose.

==================================================================

EDIT: Consider below sample excerpts from these links:

http://geekswithblogs.net/BlackRabbitCoder/archive/2011/07/28/c.net-little-pitfalls-default-parameters-are-compile-time-substitutions.aspx

http://geekswithblogs.net/BlackRabbitCoder/archive/2010/06/17/c-optional-parameters---pros-and-pitfalls.aspx

Pitfall: Optional parameter values are compile-time There is one thing and one thing only to keep in mind when using optional parameters. If you keep this one thing in mind, chances are you may well understand and avoid any potential pitfalls with their usage: That one thing is this: optional parameters are compile-time, syntactical sugar!

Pitfall: Beware of Default Parameters in Inheritance and Interface Implementation

Now, the second potential pitfalls has to do with inheritance and interface implementation. I’ll illustrate with a puzzle:

   1: public interface ITag 
   2: {
   3:     void WriteTag(string tagName = "ITag");
   4: } 
   5:  
   6: public class BaseTag : ITag 
   7: {
   8:     public virtual void WriteTag(string tagName = "BaseTag") { Console.WriteLine(tagName); }
   9: } 
  10:  
  11: public class SubTag : BaseTag 
  12: {
  13:     public override void WriteTag(string tagName = "SubTag") { Console.WriteLine(tagName); }
  14: } 
  15:  
  16: public static class Program 
  17: {
  18:     public static void Main() 
  19:     {
  20:         SubTag subTag = new SubTag();
  21:         BaseTag subByBaseTag = subTag;
  22:         ITag subByInterfaceTag = subTag; 
  23:  
  24:         // what happens here?
  25:         subTag.WriteTag();       
  26:         subByBaseTag.WriteTag(); 
  27:         subByInterfaceTag.WriteTag(); 
  28:     }
  29: } 

What happens? Well, even though the object in each case is SubTag whose tag is “SubTag”, you will get:

1: SubTag 2: BaseTag 3: ITag

But remember to make sure you:

Do not insert new default parameters in the middle of an existing set of default parameters, this may cause unpredictable behavior that may not necessarily throw a syntax error – add to end of list or create new method. Be extremely careful how you use default parameters in inheritance hierarchies and interfaces – choose the most appropriate level to add the defaults based on expected usage.

==========================================================================

S2S2
  • 8,322
  • 5
  • 37
  • 65
  • 1
    While you can certainly choose your own guidelines and best practices, I would rather not litter my code with (otherwise) unnecessary keywords (although this particular case makes me uneasy as well). – Christian.K Jan 18 '12 at 12:43
  • @Christian.K I would rather tell myself, others (reading the code) and the compiler which method/property to be used always rather than make anyone to think and pick, which does create an overhead and now the ambiguity.. – S2S2 Jan 18 '12 at 13:30
  • 2
    Calling redundant code a “best practice” is rather dubitable. You don’t need to tell the compiler (or reader) which object you are calling the method on, you already *are* doing this, since `this` is implied (never mind this quirk). – Konrad Rudolph Jan 18 '12 at 13:35
  • @KonradRudolph I think you need to be sometimes redundant to make your code more readable and easy to understand.. but not sure, i m still learning though.. – S2S2 Jan 18 '12 at 14:01
  • 1
    I find the extra verbosity of frequent `this.` makes code less readable and harder to understand. Changing optional parameters on overrides is already recognised by some tools as not best practice, and that seems a wiser thing to prohibit (indeed, optional parameters on abstract, virtual or overridden methods seems unwise to me). – Jon Hanna Jan 18 '12 at 14:06
  • @Fernando or rather... it *should* be redundant (with a few obvious caveats, such as variable/parameter conflicts, and extension methods on the current instance); it looks like it isn't in this case - whether by design or by a ... "feature" – Marc Gravell Jan 18 '12 at 14:15
  • @MarcGravell: `this.` should only ever be needed for disambiguation. We all should have been taught that one day 1 :) – leppie Jan 18 '12 at 14:55
  • @leppie and extension methods... never forget the extension methods ;p But: I don't disagree – Marc Gravell Jan 18 '12 at 15:01
1

This I think is because these default values are fixed at the compile time. If you use reflector you will see the following for MyMethod2 in BBB.

public override void MyMethod2()
{
    this.MyMethod("aaa");
}
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
chandmk
  • 3,496
  • 3
  • 21
  • 26
  • Yes, they are fixed at compile time. The question is, why doesn't the compiler consider the own class' overload, but rather uses the base class. – Christian.K Jan 18 '12 at 12:46
  • I don't think that was ever in question; the question, rather, is why has the compiler chosen that part of the method-group, and hence that default value? It is not sufficient to say "it chooses the base group", as that conflicts with the *first* usage shown, where "bbb" is printed. – Marc Gravell Jan 18 '12 at 12:48
  • @MarcGravell Forgive me, but was that last comment in response to mine. If so, I didn't quite get it :-) – Christian.K Jan 18 '12 at 12:54
  • @Christian.K no, it was to user6130 – Marc Gravell Jan 18 '12 at 12:55
  • I agree, this observation should have been a comment rather than an answer – chandmk Jan 18 '12 at 13:17
  • @user6130 and others, please see my answer edit: The cut is that the optional parameters should be used at the most preferred level (rather super level) in the inheritance chain and not in the child classes, as explained in the sub-merged links.. – S2S2 Jan 18 '12 at 13:49
0

Either Way It Needs A Fix

I would definitely regard it as a bug, either because the results is wrong or if the results are expected then the compiler should not let you declare it as "override", or at least provide a warning.

I would recommend you to report this to Microsoft.Connect

But Is It Right Or Wrong?

However regarding whether this is the expected behavior or not, let us first analyze the two views on it.

consider we have the following code:

void myfunc(int optional = 5){ /* Some code here*/ } //Function implementation
myfunc(); //Call using the default arguments

There are two ways to implement it:

  1. That optional arguments are treated like overloaded functions, resulting in the following:

    void myfunc(int optional){ /* Some code here*/ } //Function implementation
    void myfunc(){ myfunc(5); } //Default arguments implementation
    myfunc(); //Call using the default arguments
    
  2. That the default value is embedded in the caller, thus resulting in the following code:

    void myfunc(int optional){ /* Some code here*/ } //Function implementation
    myfunc(5); //Call and embed default arguments
    

There are many differences between the two approaches, but we will first take a look on how the .Net framework interprets it.

  1. In .Net you can only override a method with a method that contains the same number of arguments, but you cannot override with a method containing more arguments, even if they are all optional (which would result in a call haveing the same signature as the overridden method), say for example you have:

    class bassClass{ public virtual void someMethod()}
    class subClass :bassClass{ public override void someMethod()} //Legal
    //The following is illegal, although it would be called as someMethod();
    //class subClass:bassClass{ public override void someMethod(int optional = 5)} 
    
  2. You can overload a method with default arguments with another method with no arguments, (this has disastrous implications as I will discuss in a moments), so the folloing code is legal:

    void myfunc(int optional = 5){ /* Some code here*/ } //Function with default
    void myfunc(){ /* Some code here*/ } //No arguments
    myfunc(); //Call which one?, the one with no arguments!
    
  3. when using reflection one must always provide a default value.

All of which are enough to prove that .Net took the second implementation, so the behavior that the OP saw is right, at least according to .Net.

Problems With the .Net Approach

However there are real problems with the .Net approach.

  1. Consistency

    • As in the OP's problem when overriding the default value in an inherited method, then results might be unpredictable

    • When the original implantation of the default value is changed, and since the callers don't have to get recompiled, we might end up with default values that are no longer valid

    • Reflection requires you to provide the default value, which the caller doesn't have to know
  2. Breaking code

    • When we have a function with default arguments and latter we add a function with no arguments, all calls will now route to the new function, thus breaking all existing code, without any notification or warning!

    • Similar will happen, if we later take away the function with no arguments, then all calls will automatically route to the function with the default arguments, again with no notification or warning! although this might not be the intention of the programmer

    • Furthermore it does not have to be regular instance method, an extension method will do the same problems, since an extension method with no parameters will take precedence over an instance method with default parameters!

Summary: STAY AWAY FROM OPTIONAL ARGUMENTS, AND USE INSTEAD OVERLOADS (AS THE .NET FRAMEWORK ITSELF DOES)

yoel halb
  • 12,188
  • 3
  • 57
  • 52
0

Agree in general with @Marc Gravell.

However, I'd like to mention that the issue is old enough in C++ world (http://www.devx.com/tips/Tip/12737), and the answer looks like "unlike virtual functions, which are resolved at run time, default arguments are resolved statically, that is, at compiled time." So this C# compiler behavior had rather been accepted deliberately due to consistency, despite its unexpectedness, it seems.