Default arguments are not perfect

My previous entry on default arguments raised a few questions. I thought it’d be worth writing another short entry to make a few things clearer.

The first thing is that default arguments could certainly have been implemented by overloads under the covers or in some other way that was version resillient. However, the CLR supports default arguments natively because: a) they were already a fundamental part of C++ and VB and b) they were already a fundamental part of COM. Rather than imposing a lot of overhead on the interop layer and the two languages, it seemed best to just implement default arguments as default arguments (especially since they’re only a tiny bit of extra metadata as far as the CLR is concerned). And, as I said in my first entry, not everyone agrees on the seriousness of the versioning question in the first place.

James Slaughter is right in that default arguments certainly introduce some interesting dilemmas for languages that support overriding and delegates. We had to work through those questions in the VS 2002 timeframe, but by taking a pretty conservative stand (I believe we just require you to restate the defaults exactly), I think it ends up being pretty clear and usable. I’m not sure how C++ approaches the problem, though.

Brad asks (more or less) “If all languages supported default arguments, then when would you use one over the other?” I think the answer, like so many class library questions, boils down to questions of aesthetics which are never easy to quantify. As I touched on in my original entry, I think in most cases overloads make a lot of sense. But there are some places where default arguments can make more sense. The best example I can think of is a Print method that optionally prints a newline. The BCL deals with this issue by creating two names, Print and PrintLine:

Sub Print(ByVal Value As Object)
Sub PrintLine(ByVal Value As Object)

This is an entirely valid way to deal with the situation. But in practice, you could also use default arguments to elegantly finesse the situation:

Sub Print(ByVal Value As Object, Optional ByVal NewLine As Boolean = True)

I’ve found myself doing this in the past when working on the compiler because it’s an easy way to extend a well-established method without having to create a whole new name.

Anyway, the point isn’t so much that default arguments are perfect, just that they can be useful in situations. I think one could come up with good design guidelines to allow choosing between the two, and maybe someday I’ll sit down and try to dream them up. Until then, we’ll all just have to muddle along…

4 thoughts on “Default arguments are not perfect”

  1. C++ does default arguments like so: you can specify defaults at any level in the class hierarchy, the defaults are applied at the call site, and the _static_ type of the object/pointer/reference controls which default values (if any) are used.

    This can lead to surprised programmers who have called an overridden derived method through a base class pointer or reference and found that the base class defaults were used, not the derived class.

    I assume the CLR was implemented this way to avoid surprising the C++ programmers 😉

  2. Despite your good example of a function that calls for default arguments, if I had to see it from a C# programmer standpoint, I would say Print is begging for overloading. Beyond the aesthetic aspect (i.e., I would like itellisense to tell me I have to option to add one parameter when there is overloaded function that fits), I still think there is nothing in default arguments that overloading can’t do, unless we use VB named parameters.

  3. I personally use the following guidelines to choose between overloading, optional parameters and (when it comes) generics:

    *use overloading when a function performs an operation using parameters that are of different types but serve a similar purpose. Further, keep the number of arguments in all overloads the same. E.g

    ‘get employee by using their
    ’employee number to identify them
    Function GetEmp(ID as Integer)

    ‘get employee by using the name
    ‘to identify them
    Function GetEmp(ID as String)

    *use generics when 1) the algorithm for a function performs an operation using parameters that are of different types but serve a similar purpose and 2) the algorithm is exactly the same regardless of the types. E.g with our employee example, i wouldnt use generics because you cant use the same exact same algorithm to search by employee ID and by employee name. In a database scenario, the SQL would be slightly different and cant be generalised.

    *finally use optional parameters (in typically unrelated functions) where a function can provide an intuitive default for a parameter that is always passed the same value in many different call sites.

    In the case of VB as it currently stands (ie without generics) I would use optional parameters as opposed to the overloading,in order to achieve optionality. This is primary because it avoids having many little functions that do nothing than call other functions passing the default value manually.; because it improves Intellisense; because it reduces the amount of (trivial) code which has to be written AND maintained; because it prevents the need to duplicate documentation. In short, its because adding a new function has more implications and cascading effects thatn using an optional parameter.


  4. Your article title is not perfect. Try "overloading isn’t perfect." When you write about default arguments then use that title.

Leave a Reply