C# 4.0 - Named and Optional Parameters - Behind the Scenes
In my previous post, I talked very briefly about both named and option parameters. Now, I'd like to go a little deeper into the subject to see what this looks like underneath the covers. As before, we'll look at the limitations as well that we faced in the previous post.
Optional Parameters
First, let's define our class that will contain our optional parameters. This will be a reuse of our class that we defined in the last post of the TextBoxInfo class. We will define a constructor, just for example sake, with all parameters being optional. Not that I'd recommend that, but let's just at least look at the possibility.
{
private readonly string text;
private readonly float size;
private readonly float width;
private readonly Color color;
public TextBoxInfo(
string text = "",
float size = 10.0f,
float width = 50.0f,
Color color = new Color())
{
this.text = text;
this.size = size;
this.width = width;
this.color = color;
}
public string Text { get { return text; } }
public float Size { get { return size; } }
public float Width { get { return width; } }
public Color TextColor { get { return color; } }
}
The real interesting part of this code will be in the how the constructor looks. This is where we'll be setting the default values. Let's look through .NET Reflector to see the generated code for the constructor.
[Optional, DefaultParameterValue("")] string text,
[Optional, DefaultParameterValue(10f)] float size,
[Optional, DefaultParameterValue(50f)] float width,
[Optional] Color color)
{
this.text = text;
this.size = size;
this.width = width;
this.color = color;
}
What's interesting to note is the use of the System.Runtime.InteropServices.OptionalAttribute and the System.Runtime.InteropServices.DefaultParameterValueAttribute. The OptionalAttribute is used to specify that the parameter is optional, and the DefaultParameterValueAttribute specifies the value should the value not be specified. Given that our text, size and width are all primitives, we can use the DefaultParameterValueAttribute to specify the default value. For our color parameter, since it's a complex type, its value cannot be set through optional parameters, therefore, its default value is specified by the Color default constructor.
Let's compare that implementation to the F# implementation.
?size:float,
?width:float,
?color:Color) =
let text = defaultArg text ""
let size = defaultArg size 10.
let width = defaultArg width 50.
let color =
match color with
| None -> Color.Red
| Some c -> c
member x.Text = text
member x.FontColor = color
member x.Width = width
member x.Size = size
Once again, let's turn our eyes to the constructor details of the F# implementation. Opening up Reflector, we'll take a look at how default values are handled via the constructor. How does this differ from the C# implementation?
[OptionalArgument] Option<string> text,
[OptionalArgument] Option<double> size,
[OptionalArgument] Option<double> width,
[OptionalArgument] Option<Color> color)
{
Program.TextBoxInfo @this = this;
@this.text@5 = text;
@this.size@5 = size;
@this.width@5 = width;
@this.color@5 = color;
@this.text@7 = Operators.defaultArg<string>(@this.text@5, "");
@this.size@8 = Operators.defaultArg<double>(@this.size@5, 10.0);
@this.width@9 = Operators.defaultArg<double>(@this.width@5, 50.0);
Option<Color> option = @this.color@5;
@this.color@10 = (option == null) ? Color.Red : option._Some1;
}
From the above syntax, you will notice the use of the defaultArg function which sets the value of our member values on those which are simple types. For more complex situations, we can then use pattern matching to define the default value of our color value. This is a nice implementation as it allows me to initialize complex types in a rather controlled way as well as the simple types.
One more example I want to look at is the indexed properties and what they look like. Let's take an example of a wrapper for the much maligned DataRow object. I'll add values for the column name, and optionally the DataRowVersion enum. The code to wrap should look much like this:
{
get
{
return this.row[column, version];
}
}
Popping this open in Reflector once again shows us that the DefaultParameterValueAttribute is set to the DataRowVersion.Current value, which in this case is 0x200. As enums aren't strictly typed as they are in Java, the C# compiler can then turn the enum value into a primitive.
string column,
[Optional, DefaultParameterValue(0x200)] DataRowVersion version)
{
return this.row[column, version];
}
Having looked this over, let's move onto the named parameters implementation.
Named Parameters
Named parameters gives us the option of specifying the parameters by name, rather than by absolute position. This is quite handy when dealing with a method signature with many parameters, and especially ones not needed. You simply need to look at the managed Microsoft Office Libraries for any further proof of that. But, you don't always have to use named parameters when there are optional parameters. Instead, you can use them pretty much any time you like. Let's take our above TextBoxInfo class and create three instances, and each one will set a different combination of parameters.
{
var t1 = new TextBoxInfo(
text: "Hello World",
size : 12.0f);
var t2 = new TextBoxInfo(
size : 10.0f,
width : 45.0f);
var t3 = new TextBoxInfo(
text : "Hi there",
width : 50.0f);
Console.WriteLine(t1.Width);
Console.WriteLine(t2.Text);
Console.WriteLine(t3.Size);
}
What I'm writing out is the default values for each of the parameters I didn't set. Popping up Reflector once again, let's look at the generated code.
{
string CS$0$0000 = "Hello World";
float CS$0$0001 = 12f;
TextBoxInfo t1 = new TextBoxInfo(CS$0$0000, CS$0$0001, 50f);
CS$0$0001 = 10f;
float CS$0$0002 = 45f;
TextBoxInfo t2 = new TextBoxInfo("", CS$0$0001, CS$0$0002);
CS$0$0000 = "Hi there";
CS$0$0001 = 50f;
TextBoxInfo t3 = new TextBoxInfo(CS$0$0000, 10f, CS$0$0001);
Console.WriteLine(t1.Width);
Console.WriteLine(t2.Text);
Console.WriteLine(t3.Size);
}
What the compiler is doing behind the scenes is creating inline instances of our named parameters and feeding it to the constructor. As you will notice, it's feeding the named instances as well as putting in the default value that we specified in the method signature. Nothing really special about it. Very interesting to see how some of these challenges are handled underneath the covers.
Wrapping it Up
As you can see, named and optional parameters can be very handy to use, whether in some connected fashion or not. We can use these in combination to deal with other languages and libraries in a rather flexible fashion. It's about time C# 4.0 caught up in this regard. I have a wish list continued for C# 5.0 which still includes syntactic sugar for allowing for mixins, discriminated unions, tuples, pattern matching, immutable types, and so on. The list could go on for a while... As I said before, download the Visual Studio 2010 and .NET Framework 4.0 CTP and give it a spin.
Also, be sure to catch the C# 4.0 team on a podcast with Mads Torgersen, Anders Hejlsberg and Eric Lippert: