C# and Optional Parameters

I talked a little at DevWeek about C# 4 and Visual Basic 10 and, in the C# section of that talk, I talked about optional parameters and named parameters coming into the language.

I presented an example class something like this one;

  class Person
  {
    public void Walk()
    {
      Walk(1);
    }
    public void Walk(int howFar)
    {
      Walk(1, 3.0);
    }
    public void Walk(int howFar, double speed)
    {
      Walk(1, 3.0, false);
    }
    public void Walk(int howFar, double speed, bool comeBack)
    {
    }
  }

with a method like Walk here which ends up having a number of overloads because I want to offer the calling programmer a bit of convenience around how many (or few) arguments they need to pass to the function.

I’ve always found it a bit painful to have to write N methods like this when, really, there’s only one method called Walk and we just want to mark a number of parameters as optional which is exactly what C# 4 will let us do.

So, in C# 4 we can do something like;

  class Person
  {
    public void Walk(int howFar = 1, double speed = 3.0, bool comeBack = false)
    {
    }
  }

and I feel a bit better about writing that than I do around the 4 methods that I had to write previously and a caller has a choice between providing no parameters and up to three parameters when they call this Walk() function and they can provide those parameter values by position or by name.

At DevWeek, a delegate asked me about the coupling between a caller of a method like this one and the implementation of the method.

That is – given that the default value for the parameter howFar above is 1, where does that default value get written into the code when a caller does not provide a value for howFar. Is it at the site of the function call or is it at the site of the function implementation?

From a quick bit of disassembling with Reflector, it looks like the values for the default parameter values are dropped into the call to the function which is probably what I would have expected.

Let’s say that I build a version of Walk() where the speed is defaulted to 3.0.

Looking at it in the case where the default values are built into the caller;

  1. Someone consumes that version of Walk and doesn’t pass the speed parameter so they get the default of 3.0 baked into their calling code.
  2. I update my version of Walk() to change the default value for speed to be 4.0
  3. If the calling assembly isn’t recompiled but ( if the assembly reference is a loose one ) it does pick up the new version of Walk() at runtime then the behaviour doesn’t change because the caller is still passing 3.0 embedded into the caller.
  4. If the calling assembly is recompiled then its behaviour changes because it’s now passing 4.0.

Looking at the case where the default values are built into the callee ( not immediately obvious how you would do this but I guess the compiler could generate some Walk_$Default method or something and apply the default values there ).

  1. Someone consumes that version of Walk and doesn’t pass the speed parameter so they get the default of 3.0.
  2. I update my version of Walk() to change the default value for speed to be 4.0
  3. If the calling assembly isn’t recompiled it does pick up the new version of Walk() and the behaviour changes.
  4. If the calling assembly is recompiled then its behaviour changes.

So, the first one seems like it offers a better route but the second one seems to match more closely with what would happen in C# 3.0 as in my original example at the top of the post.

The more I think about this, I think the less that I like methods that have these optional parameters whether in C# 3.0 or C# 4.0 but I see a lot of them around so I’d guess that the Framework guidelines would have something to say about them and the best way of writing them.

I think what I’d like to see in VS2010 around this is;

  1. The IntelliSense to always show you the values of the optional parameters that you are/aren’t passing to a method – from what I’ve seen so far, it does seem that this might be there.
  2. The IntelliSense to offer you the option to copy the default values to the call-site so that you can still get the productivity benefit of default values but equally be 100% clear about what values you are actually passing to the function. Then, if the defaults change on the implementation side your calling code shouldn’t be affected.

Just my 2p!