Default arguments and versioning


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">I was recently asked about why the
CLS (and the Design Guidelines) discourages default arguments. style="mso-spacerun: yes">  So I added this annotation to the Design
Guidelines document.  I believe it
is the same reason why C# (rightfully) left it out of the
language. "urn:schemas-microsoft-com:office:office" />


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> 


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> 


style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 4pt; BORDER-TOP: windowtext 1pt solid; PADDING-LEFT: 4pt; BACKGROUND: #e9fdf3; PADDING-BOTTOM: 1pt; MARGIN-LEFT: 0.25in; BORDER-LEFT: windowtext 1pt solid; MARGIN-RIGHT: 0in; PADDING-TOP: 1pt; BORDER-BOTTOM: windowtext 1pt solid; mso-border-alt: solid windowtext .5pt; mso-element: para-border-div">

style="BACKGROUND: #e9fdf3; MARGIN: 6pt 0in; mso-add-space: auto"> face=Verdana size=1> style="mso-bidi-font-size: 10.0pt">The issue with default arguments and
versioning is that once you define a default argument you can never change its
value.  Consider the following
method (in VB as C# does not support default arguments; the basic issue exists
in C++ as well).


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face=Verdana size=1> style="mso-spacerun: yes">   face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-family: 'Times New Roman'">Public
Shared Function ComputeTotal(ByVal subtotal As Double,


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">                                    
Optional ByVal salesTaxPrecent As Double = 8.8) As
Double


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">       
ComputeTotal = subtotal + subtotal * salesTaxPrecent /
100


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">    End
Function


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face=Verdana size=1> style="FONT-SIZE: 7.5pt; FONT-FAMILY: Verdana"> 


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face=Verdana size=1> style="FONT-SIZE: 8pt; FONT-FAMILY: Verdana; mso-bidi-font-size: 10.0pt">Call
site:


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" size=1> style="FONT-SIZE: 8pt; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt">Foo. face="Courier New" size=1> style="FONT-SIZE: 8pt; FONT-FAMILY: 'Courier New'">ComputeTotal(100) face="Courier New"> style="FONT-FAMILY: 'Courier New'">


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face=Verdana size=1> style="FONT-SIZE: 7.5pt; FONT-FAMILY: Verdana"> 


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face=Verdana size=1> style="FONT-SIZE: 8pt; FONT-FAMILY: Verdana; mso-bidi-font-size: 10.0pt">Looking
at the IL disassembly for this call is instructive in showing what is going
on:
style="FONT-SIZE: 8pt; FONT-FAMILY: Verdana"> style="mso-spacerun: yes">   face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-family: 'Times New Roman'">IL_0001: style="mso-spacerun: yes">  ldc.r8 style="mso-spacerun: yes">    
100. style="COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-family: 'Times New Roman'">


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">  IL_000a: style="mso-spacerun: yes">  ldc.r8 style="mso-spacerun: yes">    
8.8000000000000007


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">  IL_0013: style="mso-spacerun: yes">  call style="mso-spacerun: yes">       float64
ConsoleApplication5.Foo::ComputeTotal(float64,


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">                                                                    
float64)


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=3> style="FONT-SIZE: 12pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-family: 'Times New Roman'"> 


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face=Verdana size=1> style="FONT-SIZE: 8pt; FONT-FAMILY: Verdana; mso-bidi-font-size: 10.0pt">Notice
in line 000a the VB compiler generated the IL instruction to load the literal
8.8.  This is burned into the call
site.  If we change the definitions
of the default value for salesTaxPrecent to 7.8% and do not recompile the
calling code (as is common when working with large frameworks) 8.8 will continue
to be passed as the default value. 
This can be a very nasty bug to track down!


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face=Verdana size=1> style="FONT-SIZE: 8pt; FONT-FAMILY: Verdana; mso-bidi-font-size: 10.0pt">Using
method overloading encapsulates the default value so that it can be changed in a
predictable way.


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face=Verdana size=1> style="FONT-SIZE: 7.5pt; FONT-FAMILY: Verdana"> 


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">   Public Shared Function
ComputeTotal(ByVal subtotal As Double, ByVal salesTaxPrecent As
Double)


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">                                      
As Double


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">       
ComputeTotal = subtotal + subtotal * salesTaxPrecent /
100


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">    End
Function


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=3> style="FONT-SIZE: 12pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-family: 'Times New Roman'"> 


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">    Public Shared Function
ComputeTotal(ByVal subtotal As Double) As Double


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">       
ComputeTotal = ComputeTotal(subtotal, 8.8)


style="BACKGROUND: #e9fdf3; MARGIN: auto auto auto 0.25in; mso-add-space: auto; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto"> face="Courier New" color=blue size=1> style="FONT-SIZE: 8pt; COLOR: blue; FONT-FAMILY: 'Courier New'; mso-bidi-font-size: 10.0pt; mso-bidi-font-family: 'Times New Roman'"> style="mso-spacerun: yes">    End
Function


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> 


 

Comments (12)

  1. Mark Hurd says:

    So don’t do that.

    I’m sure there’ll be Attribute changes that cause the same problem.

    Which is preferable: Highlight to API designers that they shouldn’t use "magic" defaults the might change, or require all APIs to document what the default values are for otherwise unmentioned ‘arguments’ to overloaded methods?

    And they’re good for scripting languages too.

    (Note that overload methods *do* have their uses, but providing default argument values is a minor one — nevertheless it *is* the valid approach in the scenario described in this post. )

  2. Dan Sullivan says:

    But C# hasn’t left the effect of this out of the language. For example,

    public class foo
    {
    public const int goo = 3;
    }

    will produce the same problem if the value of goo is changed and an update is done without rebuilding the users of the assembly. Note that the current guidelines recommend using const when you can.

    If the VB guideline were applied to C# you would have to discouge the use of const fields and replace them with static readonly properties.

    Maybe the current guideline should add a recommendation that const field be considered immutable, much like COM interfaces.

    Likewise a recommendation could be added that default values should be considered immutable too. It seems like telling people how to use something that is in the language safely would be better than telling them not to use it at all. I’m sure that there is lots of VB code being ported that has default values, so it is not like they are going to go away.

  3. rizzo says:

    Brad,

    How did VB handle default (or optional parameters) in COM. Wouldn’t we be faced with the same problem?

  4. ries says:

    VB5 optionals were much more like a named and open-ended, but untyped parameter list

    public function f (a as integer, optional b as variant) as integer
    if ismissing(b) then b=2
    f=a*b
    end function

    Also, on caller side, VB allows specifying naming the parameters in the method call (while C# only allows this for Attributes).

    Can anyone give a reason why C# developers are not allowed to write richer code, like ComputeTotal = ComputeTotal(subtotal, salesTaxPercent:=8.8) ?

    I really miss being able to do this.

  5. Jakub Skopal says:

    Hi,

    you are writing always something about Design Guidelines. Is there any place, where to download up-to-date complete version of them?

    thanks

  6. Tor says:

    Seems to me that the real issue with the example you give is that default arguments is not supported in IL, so the VB compiler has to cheat by actually figuring out what the default argument is and supplying that.

    So the question isn’t so much whether C# should support it, but rather whether IL should. If it did, there would be no reason why C# shouldn’t support it too (at least not THAT reason).

  7. Not sure if I buy this as a valid reason not to include default parameters.
    * First of all, when you change a method signature it only makes sense to recompile all classes that call this method – I’m sure a param-type change from say int to double would cause problems as well.
    * Second of all, like Dan said, you still have this problem if you add public constants to your class. I would assume enum’s also would pose a problem when changed
    * And last but not least, instead of embedding the default value on the calling end, why not implement this in the compiler by simply generating extra overloaded functions that call into the main function will all default values filled in?

  8. Keith Patrick says:

    I used to use static readonly instead of const, but I ran into a problem where I couldn’t use my readonly values as attribute arguments, and since I do not use magic numbers at all, I had to revert to using consts. A bit of a pain, since const ended up causing problems elsewhere, so it came down to lesser of two evils, which is a choice I hate to make.

  9. dannyR says:

    Re: Dan Sullivan’s comment. Yes, your example shows the same issue exists. However, it goes away if the code looks like this:

    public class foo {
    private const int goo = 3;
    public int Goo { get { return this.goo; }}
    }

    It doesn’t completely negate your point, except that const fields don’t have to have the problem you described if used this way.

  10. Shane King says:

    Couldn’t the compiler see something like this (using C style syntax here):

    void foo(int i = 0)
    {
    }

    And produce code as if this had been written:

    void foo()
    {
    foo(0);
    }

    void foo(int i)
    {
    }

    That way, clients would automatically be updated, and you can keep your default values for arguments. Seems like a best of both worlds solution to me. One issue is that it might clash with an existing overload, but you’ve got that problem either way, so I don’t see how this changes it.

  11. Jazper says:

    just the fact that people had to recomile it to take effect is a shitty reason for that problem. Why not just say, that C# does not support default parameters. its just because Microsoft decided to be so. it had not been impossible!

  12. C# 2.0 -- WHY DEFAULT PARAMETER VALUES? #### says:

    While the C# designer(s) may have had good intentions by not including

    default parameters in the C# language, it is better "style" to inform

    developers of the impact of one’s decision instead of removing the

    choice entirely. If "goto" can make it into the language, surely something

    as useful as default parameter values could make it as well. (Even attributes

    have, in effect, default parameters … not to mention optional and named parameters.)

    The issue that Microsoft has raised is important: default parameter values

    technically should be part of the interface. The significance of that fact does

    not imply that default parameters are not good design. To the contrary, the

    clunky approaches that developers (and several C# publications) have advised as

    work-arounds are a far worse outcome than if default parameters were used.

    While the work-around suggested by Microsoft does not cause end-users of

    such classes to re-compile, it instead pretends that the "default value"

    chosen is un-important to the end-user (by removing it from the interface).

    If an end-user of this class were to assume a certain default value and it

    was later changed by the developer, then surely the end-user *should* be

    notified. Besides increasing the code burden for the designer of a class,

    this method can clutter the interface with a huge set of needless signatures.

    A second approach commonly suggested is the use of a params object array.

    This technique is used frequently in un-typed languages such as JavaScript.

    However, this design choice removes the ability to cleanly type one’s

    parameters and also prevents the interface from advertising the default

    values chosen by the class implementation.

    The lack of default parameters, a relatively modern language enhancement, is

    just silly. The removal of this feature (from the languages that C# is

    based) is indeed a bad design choice. It is rather strange that a language

    that gives developers the choice to use the "goto" statement does not give

    developers the choice to publish and use default parameter values with their

    class interface.

    Eric Gunnerson has stated that customer feedback is the only way to make this change happen. So, make a difference by making your opinion heard on this. Go to http://www.gotdotnet.com/Community/MessageBoard/Thread.aspx?id=228307 and post your throughts. There are technical reasons on both sides of the argument, but it really boils down to style and utility. The MS language designers do seem to listen to the feedback (if you scream it in the right place).

Skip to main content