Thoughts on C# 4.0’ optional parameters

C# 4.0 is just round the corner and along with it set of nice new additions to the language, including optional parameters. There’s been some historical resistance to add this feature to the language, but here’ it is, and I’m glad it’s coming, or at least I was.

In few words, optional parameters, have their default value specified in the signature of the method. You can then skip them when calling method, and the method will be called with their default values.

So, what’s the deal?

To simplify the current discussion I will refer to the method containing default parameters (Foo in this example) as called method, and to method providing the default value (DateTime.Now getter in the example few paragraphs below) as value provider.

Take a look at the code below. Method Foo has two parameters, but we can call them as if it had none.

defaultParameters_0

Looking good so far, right? Let’s take a look at how Main method looks like in Reflector.

defaultArguments_1

As we can see there’s no magic here – simple compiler trick. Compiler binds the invocation to the method, and them puts the arguments for you. The code looks as if you had written it yourself in earlier version of C#.

Still good, right? So how does exactly the compiler knows what to put on the calling side? Let’s take a look at the compiled signature of the method Foo.

defaultParameters_2

Ha! The values are encoded in DefaultParameterValueAttribute. Do you see the problem here? No? Than let’s try something else. Let’s change the signature of the method to take DateTime instead of int.

defaultParameters_3

Notice we initialize the time to default value of DateTime.Now. All good? So let’s compile the code, shall we?

defaultParameters_4

Oops.

Turns out we can’t. What seems like a really reasonable code is not allowed. Since the default values for arguments are being kept in attributes they must be a compile time constants, which includes primitive types (numeric types and strings), tokens ( typeof, methodof, which is not exposed in C# though ) and nulls. Pretty disappointing right?  This means no new Foo(), no Foo.Bar is allowed. This dramatically limits the range of scenarios where this feature can be used, as most of the time, you’ll want not null, but something else as your default value, in which case you’ll end up creating overloads anyway.

There are some workarounds, like the one described in this book, but they have their own downsides, and it’s not always possible to use them anyway.

This all makes me think – why did the authors of the language decided to provide such crippled implementation of the feature? I’m not an expert in this matter, but I found a simple way in which the feature could be implemented allowing for far greater range of scenarios.

What if…

Let’s re-examine how this feature works.

  • The compiler puts the default values of the arguments in a special attribute type on the called method signature.
  • On the calling side, the compiler reads the values, and puts these of them that were not provided explicitly.

All the work happens at compile time (let’s ignore the dynamic for a moment, we’ll get to that as well) so no additional work at runtime is required. Since the compiler is very powerful, why not go one step further.

I think by simply extending this approach the following scenarios could be enabled.

  • using value returned by static parameterless method (this includes static property getters) as default value.
  • using value returned by instance parameterless method defined on the same type (or base type of the type) as called method is defined on (this would only be applicable for instance methods).
  • using default, parameterless constructors as default value.
  • or if we wanted to extend it further: using value returning by any variant of the above that does take parameters that are allowed to be put in the attribute (including both constants, and values of other parameters).

Let us examine how this could be (I think) achieved.

The spoon does not exist

What we would need is a way to store information which method, or constructor of which type we want to invoke in case no default value is provided. Since tokens are legal in attributes, the existing approach could be extended with something like this:

defaultParameters_5 

We have a way of storing the method. Now, the compiler could easily retrieve the token and invoke the called method.

  • If value provider is static there’s no problem – just call it, regardless of whether called method is an instance or static method.
  • If value provider is instance method, and called method is instance method as well, invoke the value provider on the instance on which called method is being invoked (hence the requirement that value provider must be declared on the same type as called method or its base type).
  • If value provider is instance method but called method is static do not allow (at compile time!) this code to compile, since there’s no instance to call the value provider on.

When it comes to constructors it is even simpler – since we allow only default constructor, at compile time we would check if the type does indeed have default, accessible constructor, and disallow the code to compile otherwise. Then when the called method is being invoked, retrieve the type token and call the default constructor on that type.

What about dynamic code?

I don’t have very intimate knowledge of dynamic code yet, but I think this could work with dynamic code as well. The compiler would put all the information on the call site (new concept introduced in .NET 4.0) and this would be all we need. In C# 1.0 having MethodInfo of a method you could invoke it. Same having System.Type it is easy to find and invoke it’s default constructor using little bit of reflection. I really see no reason why this would not work.

 

What do you think – am I missing part of the picture – is it something terribly wrong with my idea that would restrain it from working? Or maybe C# team didn’t really want (as they used to) implement this feature so they provided only the minimal implementation they needed for other major features, COM interop specifically.

Technorati Tags:

Comments

I’m also not perfectly sure.. Your implementation looks reasonable, but I am partly on the .NET Team’s side here because I think that anything besides compile time constants should be explicitly expressed by overloads as we used to do.

I see the major benefit here not in Com interop (that’s what dynamic is for), but in more visible defaults for methods with overloads.