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;
- 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.
- I update my version of Walk() to change the default value for speed to be 4.0
- 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.
- 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 ).
- Someone consumes that version of Walk and doesn’t pass the speed parameter so they get the default of 3.0.
- I update my version of Walk() to change the default value for speed to be 4.0
- If the calling assembly isn’t recompiled it does pick up the new version of Walk() and the behaviour changes.
- 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;
- 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.
- 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!