C# 4.0 New Features Part 2.1 – default parameter intrigue

Writing

With all of the new C# 4.0 stuff coming out, I feel like a kid in a candy store. Sorry for post overload, but I just can’t help myself! I also think I need to stop putting numbers on these posts, because obviously I have just completely thrown them to the curb. I hate to put a "Part 3" on this post though, since it is just an extension of a previous one.

Jonathan Pryor pointed out on my last post that the default parameter feature in C# 4.0 was implemented in the same way that that the default parameter feature in VB.net has been implemented. He also points out a seemingly obvious way that they could have made it better, but then he points out why it wouldn’t work when combined with the named parameter feature.

So, since Jonathan is a freakin’ smart guy, and most of us (including me) aren’t that smart, I am going to elaborate on his comment and explain in detail what he is talking about.

So, to start off let’s look at the implementation of default parameters in C# 4.0. It all starts with two attributes called OptionalAttribute and DefaultParameterValueAttribute. If you go look these attributes up, you will see that they have been around since since .net 1.1 and .net 2.0 respectively. The reason for this is that other languages besides C# have supported these features going back to .net 1.1. In fact, you could add a DefaultParameterValueAttribute to one of you parameters in a method in C# and it would work perfectly fine, it is just that you can not consume it in C# since C# does not support this feature (until C# 4.0).

In my previous post I created a class that looked like this:

public class TestClass
{    
    public void PerformOperation(string val1 = "val", int val2 = 10, double val3 = 12.2)
    {
        Console.WriteLine("{0},{1},{2}", val1, val2, val3);
    }
}

So, you see that this class has a method which has three default parameters. This means that we can call this method without passing any arguments to it and the default values would be "filled in" for us. So, how does the C# compiler implement this behavior?

Your first idea may be that C# just generates overloads, something that looks like this:

public void PerformOperation()
{
    PerformOperation("val", 10, 12.2);
}

public void PerformOperation(string val1)
{
    PerformOperation(val1, 10, 12.2);
}

public void PerformOperation(string val1, int val2)
{
    PerformOperation(val1, val2, 12.2);
}
        
public void PerformOperation(string val1, int val2, double val3)
{
    Console.WriteLine("{0},{1},{2}", val1, val2, val3);
}

Well, in reality, it looks like this (this is reflected code):

public void PerformOperation([Optional, DefaultParameterValue("val")] string val1, 
    [Optional, DefaultParameterValue(10)] int val2, [Optional, DefaultParameterValue(12.2)] double val3)
{
    Console.WriteLine("{0},{1},{2}", val1, val2, val3);
}

Hmmmmm. So, instead of just generating overloads for each method with the values filled in, it just applies some attributes to the parameters that declare them as optional and then specifies their default values. But, how does that work?

If you are familiar with attributes in .net then you will know that you have to use reflection to read out the properties of these attributes, and you have to have code running somewhere to process these attributes. All they are is meta-data assigned to the method, not code that executes at runtime. So, is C# doing reflection every time I call a method with default parameters? Fortunately the answer to that question is "no".

The answer to how this works may be a little bit surprising though. If we want to call the above method with no parameters:

var testClass = new TestClass();
testClass.PerformOperation();

What does this compile to? Interestingly it looks like this:

var testClass = new TestClass();
testClass.PerformOperation("val", 10, 12.2);

You’ll notice that the default parameter values for this method have just been compiled right into the calling code. The C# compiler is reading those attributes off the method and then using them to just insert the values into the calling code and then compiling them. So, what happens if I change the default values and don’t compile my entire system? Well, the calling code will still have the wrong values. That is definitely something that you will have to look out for.

So, why did they choose to implement it this way? Well, as Jonathan pointed out, if they dynamically generated overloads one thing that wouldn’t work is the new named parameters feature. Why wouldn’t it work? Well, I’m glad you asked.

Lets say we had the overloaded methods that I put in above, and I wanted to call my method like this:

var testClass = new TestClass();
testClass.PerformOperation(val3: 15.1);

Hmmm. What overload would I call? I don’t have an overload to call. Even though we generated overloads, we still can’t leave out parameters and we would be stuck with inserting values into our IL again. Then we would have a mixed system where sometimes it would bake in values, and other times it wouldn’t. No good.

Now, you might say, what about just generating overloads for all parameters in all orders? Well, since we have three parameters of different types, that would work for our particular instance. It would not work for all instances though. What if we had three string parameters? You can’t have three overloads of a method that each take three strings, method resolution would be impossible.

It appears for now that these two features just won’t interact, and I’m sure that if there was a way in the current .net runtime to make it work without baking in the values, they would have. But for now we just have to accept the way it works and move on. Maybe in the future the runtime will have a way to tag parameters with default values that can stay with the method and then use those values when parameters aren’t provided. Who knows. Hopefully you found this little adventure into the default parameter to be interesting, and hopefully you’ll come back for part 3 which will be coming along shortly.

More Insights

View All