Thoughts on C# 4.0’ optional parameters

C# 4.0 is just round the cor­ner and along with it set of nice new addi­tions to the lan­guage, includ­ing optional para­me­ters. There’s been some his­tor­i­cal resis­tance to add this fea­ture to the lan­guage, but here' it is, and I’m glad it’s com­ing, or at least I was.

In few words, optional para­me­ters, have their default value spec­i­fied in the sig­na­ture of the method. You can then skip them when call­ing method, and the method will be called with their default values.

So, what’s the deal?

To sim­plify the cur­rent dis­cus­sion I will refer to the method con­tain­ing default para­me­ters (Foo in this exam­ple) as called method, and to method pro­vid­ing the default value (DateTime.Now get­ter in the exam­ple few para­graphs below) as value provider.

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

defaultParameters_0

Look­ing 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 – sim­ple com­piler trick. Com­piler binds the invo­ca­tion to the method, and them puts the argu­ments for you. The code looks as if you had writ­ten it your­self in ear­lier ver­sion of C#.

Still good, right? So how does exactly the com­piler knows what to put on the call­ing side? Let’s take a look at the com­piled sig­na­ture of the method Foo.

defaultParameters_2

Ha! The val­ues are encoded in Default­Pa­ra­me­ter­ValueAttribute. Do you see the prob­lem here? No? Than let’s try some­thing else. Let’s change the sig­na­ture of the method to take Date­Time instead of int.

defaultParameters_3

Notice we ini­tial­ize the time to default value of DateTime.Now. All good? So let’s com­pile the code, shall we?

defaultParameters_4

Oops.

Turns out we can’t. What seems like a really rea­son­able code is not allowed. Since the default val­ues for argu­ments are being kept in attrib­utes they must be a com­pile time con­stants, which includes prim­i­tive types (numeric types and strings), tokens ( typeof, methodof, which is not exposed in C# though ) and nulls. Pretty dis­ap­point­ing right?  This means no new Foo(), no Foo.Bar is allowed. This dra­mat­i­cally lim­its the range of sce­nar­ios where this fea­ture can be used, as most of the time, you’ll want not null, but some­thing else as your default value, in which case you’ll end up cre­at­ing over­loads anyway.

There are some workarounds, like the one described in this book, but they have their own down­sides, and it’s not always pos­si­ble to use them anyway.

This all makes me think – why did the authors of the lan­guage decided to pro­vide such crip­pled imple­men­ta­tion of the fea­ture? I’m not an expert in this mat­ter, but I found a sim­ple way in which the fea­ture could be imple­mented allow­ing for far greater range of scenarios.

What if…

Let’s re-examine how this fea­ture works.

  • The com­piler puts the default val­ues of the argu­ments in a spe­cial attribute type on the called method signature.
  • On the call­ing side, the com­piler reads the val­ues, and puts these of them that were not pro­vided explicitly.

All the work hap­pens at com­pile time (let’s ignore the dynamic for a moment, we’ll get to that as well) so no addi­tional work at run­time is required. Since the com­piler is very pow­er­ful, why not go one step further.

I think by sim­ply extend­ing this approach the fol­low­ing sce­nar­ios could be enabled.

  • using value returned by sta­tic para­me­ter­less method (this includes sta­tic prop­erty get­ters) as default value.
  • using value returned by instance para­me­ter­less method defined on the same type (or base type of the type) as called method is defined on (this would only be applic­a­ble for instance methods).
  • using default, para­me­ter­less con­struc­tors as default value.
  • or if we wanted to extend it fur­ther: using value return­ing by any vari­ant of the above that does take para­me­ters that are allowed to be put in the attribute (includ­ing both con­stants, and val­ues of other parameters).

Let us exam­ine how this could be (I think) achieved.

The spoon does not exist

What we would need is a way to store infor­ma­tion which method, or con­struc­tor of which type we want to invoke in case no default value is pro­vided. Since tokens are legal in attrib­utes, the exist­ing approach could be extended with some­thing like this:

defaultParameters_5 

We have a way of stor­ing the method. Now, the com­piler could eas­ily retrieve the token and invoke the called method.

  • If value provider is sta­tic there’s no prob­lem – just call it, regard­less of whether called method is an instance or sta­tic 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 require­ment 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 sta­tic do not allow (at com­pile time!) this code to com­pile, since there’s no instance to call the value provider on.

When it comes to con­struc­tors it is even sim­pler – since we allow only default con­struc­tor, at com­pile time we would check if the type does indeed have default, acces­si­ble con­struc­tor, and dis­al­low the code to com­pile oth­er­wise. Then when the called method is being invoked, retrieve the type token and call the default con­struc­tor on that type.

What about dynamic code?

I don’t have very inti­mate knowl­edge of dynamic code yet, but I think this could work with dynamic code as well. The com­piler would put all the infor­ma­tion on the call site (new con­cept intro­duced in .NET 4.0) and this would be all we need. In C# 1.0 hav­ing Method­Info of a method you could invoke it. Same hav­ing System.Type it is easy to find and invoke it’s default con­struc­tor using lit­tle bit of reflec­tion. I really see no rea­son why this would not work.

 

What do you think – am I miss­ing part of the pic­ture – is it some­thing ter­ri­bly wrong with my idea that would restrain it from work­ing? Or maybe C# team didn’t really want (as they used to) imple­ment this fea­ture so they pro­vided only the min­i­mal imple­men­ta­tion they needed for other major fea­tures, COM interop specifically.

Tech­no­rati Tags:
  • http://www.tigraine.at/ Daniel Höl­bling

    I'm also not per­fectly sure.. Your imple­men­ta­tion looks rea­son­able, but I am partly on the .NET Team's side here because I think that any­thing besides com­pile time con­stants should be explic­itly expressed by over­loads as we used to do.

    I see the major ben­e­fit here not in Com interop (that's what dynamic is for), but in more vis­i­ble defaults for meth­ods with overloads.