Friday, October 31, 2008 9:31 PM bart

C# 4.0 Feature Focus - Part 1 - Optional parameters

Welcome to the first post in my new C# 4.0 Feature Focus series. Today we'll start by taking a look at optional parameters, a long-standing request from the community that made it to C# 4.0. By itself, the feature is definitely useful but in conjunction with the mission to make COM interop easier, there's even more value to it. In this post I'll outline what the feature looks like, how it's implemented and what the important caveats are.

 

The syntax

C# 4.0 can both declare and consume optional parameters. Here's a sample of a very simple method that declares a parameter as optional:

public static class OptionalDemoLib
{
   public static void SayHello(string s = "Hello World!")
   {
      Console.WriteLine(s);
   }
}

This means you can either call Do with one argument or without an argument, in which case the default value is used:

public static class OptionalDemo
{
   public static void Main()
   {
      OptionalDemoLib.SayHello();
      OptionalDemoLib.SayHello("Hello Bart!");
   }
}

Notice all optional parameters need to come at the end of the argument list.

optlib.cs(3,58): error CS1737: Optional parameters must appear after all required parameters

If this weren't the case all sorts of ambiguities would result, e.g.:

public static void SayHello(string s1 = "Hello World!", string s2)

What would a call with a single string argument result in? Would the parameter be bound to s1, overriding the default, or would it bind to s2?

 

The implementation

How does it work? Let's start by taking a look at the definition side. Here's the IL corresponding to the declaration of SayHello above:

.method public hidebysig static void  SayHello([opt] string s) cil managed
{
  .param [1] = "Hello World!"
 
// Code size       9 (0x9)
  .maxstack  8
  IL_0000:  nop
  IL_0001: 
ldarg.0
  IL_0002:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0007:  nop
  IL_0008:  ret
} // end of method OptionalDemoLib::SayHello

Two things are relevant here. First of all, the parameter is decorated with the [opt]. Second, the method body contains a .param directive. It turns out both of those primitives have been supported in the CLI since the very beginning. Visual Basic is one of the languages that already uses this today. Let's dive a little deeper using the CLI specification (ECMA 335), partition II:

  • 15.4    Defining methods

    opt specifies that this parameter is intended to be optional from an end-user point of view. The value to be supplied is stored using the .param syntax ($15.4.1.4).
  • 15.4.1    Method body

    | .param `[` Int32 `]` [ `=` FieldInit ]          Store a constant FieldInit value for parameter Int32.
  • 15.4.1.4    The .param directive

    This directive stores in the metadata a constant value associated with method parameter number Int32, see $22.9. (...) Unlike CIL instructions, .param uses index 0 to specify the return value of the method, index 1 to specify the first parameter of the method, ...
  • 22.9    Constant : 0x0B

    The Constant table is used to store compile-time, constant values for fields, parameters, and properties. The Constant table has the following columns:
    - Type ...
    - Parent ...
    - Value (an index into the Blob heap)

    Note that Constant information odes not directly influence runtime behavior, although it is visible via Reflection. Compilers inspect this information, at compile time, when importing metadata, but the value of the constant itself, if used, becomes embedded into the CIL stream the compiler emits. There are no CIL instructions to access the Constant table at runtime.

I'll come back to the remark in paragraph 22.9 in just a second. An important thing here is that the value needs to be constant, so no new'ing up of stuff or results of methods calls are allowed:

optlib.cs(3,23): error CS1736: Default parameter value for 's' must be a compile-time constant

What does the call-site look like if the parameter is omitted?

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       24 (0x18)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "Hello World!"
  IL_0006:  call       void [optlib]OptionalDemoLib::Do(string)
  IL_000b:  nop
  IL_000c:  ldstr      "Hello Bart!"
  IL_0011:  call       void [optlib]OptionalDemoLib::Do(string)
  IL_0016:  nop
  IL_0017:  ret
} // end of method OptionalDemo::Main

Notice how remark 22.9 applies here. At the call-site both calls look like a call with one argument. The optional argument is "compiled away" on the side of the caller.

 

The caveat

The remark above quoting the CLI specification is a very important one:

Note that Constant information odes not directly influence runtime behavior, although it is visible via Reflection. Compilers inspect this information, at compile time, when importing metadata, but the value of the constant itself, if used, becomes embedded into the CIL stream the compiler emits. There are no CIL instructions to access the Constant table at runtime.

In human words, default values are burned into the call site. The metadata specified by the .param directive is only used to keep the constant value around, but as soon as a method is called and optional parameters are used in that call (as determined by the compiler), that value gets copied literally to the call site where it sticks. Let's illustrate this:

Step 1: Compile the following (csc /t:library optlib.cs)

using System;

public static class OptionalDemoLib
{
   public static void SayHello(string s = "Hello World!")
   {
      Console.WriteLine(s);
   }
}

Step 2: Compile the following (csc opt.cs /r:optlib.dll)

using System;
using System.Reflection;

public static class OptionalDemo
{
   public static void Main()
   {
      OptionalDemoLib.SayHello();
      Console.WriteLine(typeof(OptionalDemoLib).GetMethod("SayHello").GetParameters()[0].RawDefaultValue);
   }
}

Step 3: Run opt.exe

> opt.exe
Hello World!
Hello World!

Step 4: Change the library and recompile (don't recompile the opt.cs demo caller)

using System;

public static class OptionalDemoLib
{
   public static void SayHello(string s = "Hello Universe!")
   {
      Console.WriteLine(s);
   }
}

Step 5: Run opt.exe

> opt.exe
Hello World!
Hello Universe!

In step 2 we're introducing the use of reflection to get the default value for the optional parameter. This is run-time reflection that actually inspects the metadata associated with the method's parameter. However, as 22.9 mentions: "There are no CIL instructions to access the Constant table at runtime.", so the default value gets burned into the call site by the compiler. This is no different than the same constant-ness encountered in the difference between readonly variables and constants. The key take-away from this: once you expose a default parameter value on a public method, you can never change it without recompiling all clients that depend on it. For library writers, this never means never ever. If you need the flexibility of changing defaults afterwards, consider providing overloads instead:

using System;

public static class OptionalDemoLib
{
   public static void SayHello()
   {
      SayHello("Hello Universe!");
   }

   public static void SayHello(string s)
   {
      Console.WriteLine(s);
   }
}

This way the constant remains on the definition side and can be changed over there at will. Not that you should do so regularly of course, as you're after all changing defaults that are hopefully documented somewhere in the XML comments for your public methods. Yet another way to attack the problem if you have a bunch of parameters is to take in a property "bag" as the argument to the method (in practice and object with properties for all the supported setting "parameters"). That way every value can be optional and the method can examine whether certain omissions can be granted. Dispatching to the internal implementation with the maximum parameter list could use techniques like null-coalescing (??):

public static void ComplexSayHello(Message arg)
{
   ComplexSayHelloInternal(..., arg.Text ?? "Hello Universe!", ...);
}

Use the right approach - all techniques have their benefits.

 

The past

We've talked about the future, but let me point out it was actually possible to declare optional parameters in C# before, using parameter metadata custom attributes:

using System;
using System.Runtime.InteropServices;

public static class
OptionalDemoLib
{
   public static void SayHello([Optional][DefaultParameterValue("Hello Universe!")] string s)
   {
      Console.WriteLine(s);
   }
}

This produces the same IL as the sample shown earlier using C# 4.0 syntax. C# couldn't consume this method though without specifying all parameters, but now it can.

 

Conclusion

Optional parameters are a beautiful feature and will make life easier when dealing with old COM-driven libraries that were designed with this language feature in mind (amongst others such as named parameters, see next post). To keep the picture symmetric, C# 4.0 also provides the ability to define optional parameters, but be well-aware of the call site burning fact mentioned above. Only use optional parameters if the optional value is really constant in the time dimension unless you're never going to expose the optional value to the outside world (but it's damn easy to make a previously internal or private method public and forgetting about this fact, so the first rule should be the strongest decisive factor...). Don't get me wrong: I like the feature a lot, but powerful weapons need safety warnings.

Next time: named parameters. Enjoy!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# » C# 4.0 Feature Focus - Part 1 - Optional parameters - B# .NET Blog

Pingback from  » C# 4.0 Feature Focus - Part 1 - Optional parameters - B# .NET Blog

# re: C# 4.0 Feature Focus - Part 1 - Optional parameters

Saturday, November 01, 2008 10:02 AM by Emperor XLII

Have you looked at optional parameters on interface methods as well?

# Reflective Perspective - Chris Alcock » The Morning Brew #214

Pingback from  Reflective Perspective - Chris Alcock  » The Morning Brew #214

# re: C# 4.0 Feature Focus - Part 1 - Optional parameters

Sunday, November 02, 2008 11:39 PM by Anders Dalvander

Are optional parameters supported for virtual methods? Are you allowed to change the default in derived classes?

class Base

{

  public virtual void Foo(int a = 1, int b = 2)

  {

  }

}

class Derived : Base

{

  public override void Foo(int a = 3, int b = 4)

  {

  }

}

Derived derived = new Derived();

Base base = derived;

derived.Foo(); // What is the value of a and b?

base.Foo(); // What is the value of a and b?

# re: C# 4.0 Feature Focus - Part 1 - Optional parameters

Sunday, November 02, 2008 11:49 PM by Anders Dalvander

Are skipped parameters supported, or would one need to use named parameters?

class Foo

{

  public void Bar(int a = 1, int b = 2, int c = 3)

  {

  }

}

Foo foo = new Foo();

foo.Bar(42, default, 0); // b would be 2.

# re: C# 4.0 Feature Focus - Part 1 - Optional parameters

Monday, November 03, 2008 10:26 AM by Samuel

I must say I'm curious what the rationale for this behavior is. If the default value is available at runtime, why is it burned at compile-time?

# re: C# 4.0 Feature Focus - Part 1 - Optional parameters

Monday, November 03, 2008 5:32 PM by bart

Hi Anders,

Concerning your question on virtual methods: yes they can be used in that context and can be overridden in derived types. The value used is determined based on the compile-time type for the variable, so you'll see different results depending whether you're using the base or sub at the call-site.

On "skipping" parameters, you'd have to use named parameters to accomplish this. Having a (presumable typeless) "default" keyword to form "holes" would make life for IntelliSense and overload resolution interesting though...

Thanks,

-Bart

# re: C# 4.0 Feature Focus - Part 1 - Optional parameters

Monday, November 03, 2008 6:26 PM by bart

Hi Samuel,

The restriction we're facing here is how the runtime deals with default parameter values. More specifically, they're treated as metadata. If they weren't treated this way, the logic behind a method call instruction would be far more involved - and hence more expensive - to match the called method with the intended target. In this sentence the word "match" is inappropriate as tasks such as binding are not a runtime (at least in a "runtime = VES = Virtual Execution System sense" of the word) task but a front-end language task, possibly in concern with additional libraries (to do late binding). But for the (CLR = static typing) runtime, a target method call should be fully specified.

Why shoyld the execution engine be only concerned about calling a well-specified method? Some background first to outline this is the case today. Refer to "III - 3.19 - call" for more context, stating "(...) method is a metadata token (...) that indicates the method to call, and the number, type and order of arguments (...)". If you dive deeper, you'd end up in Partition II defining MethodRef, MethodDef and MethodSpec. In other words the CIL code is unambiguously expressing the (early-bound) intent of the code. There are many cases where more intelligence in the runtime would be harmful. Front-end languages typically have betterness rules for overload resolution that are already complicated enough that a runtime "with its own mind about things" is better to avoid. Here's a sample:

void Bar(int i);

void Bar(int i, int j = 1, int k = 2);

Calling Bar(1) in C# is defined as choosing the former one (it's better than the other one, hence the word "betterness"), emitting a call instruction to Bar(int32). If the runtime were to have its own (well-defined) mind now, it could further expand this call to the latter because it might be tempted (again hypothetically) to expand a method call as much as possible. But all of this crazy runtime logic would need to be known by all front-end languages and would have the potential to seriously limit the front-end language's flexibility (i.e. "loosing control").

While C# could use reflection (quote: "There are no CIL instructions to access the Constant table at runtime.") to obtain the value of an optional parameter, it's clear that this wouldn't be an optimal solution either in terms of runtime cost, as reflection is involved.

You might wonder why methods with default parameters are not expanded to a series of overloads. This too would seriously complicate matters. Not only does this generate code out of nowhere that possibly ends up on the public contract of the assembly (other than it's the case for most other higher-level language features that generate code within strict boundaries, e.g. iterators, automatic properties, LINQ, etc), but the synergy with named parameters would be half-baked as you still want to say "call method X with only the last parameter", so you still need call-site baking of the omitted parameter's values. It's clear that with n optionals you can only emit n additional overloads, specifying one additional parameter at a time. The reason it's not possible to generate 2^n overloads, accounting for all combinations, is obviously because types on the emitted signatures could clash (signatures don't take parameter names into account at all, only their types).

Thanks,

-Bart

# C# 4.0 Feature Focus - Part 3 - Intermezzo: LINQ's new Zip operator

Monday, November 03, 2008 11:11 PM by B# .NET Blog

After named parameters and optional parameters , we'll take a little breadth and deviate a bit from

# WMOC#26 - Microsoft PDC Roundup - Service Endpoint

Thursday, November 20, 2008 9:37 PM by WMOC#26 - Microsoft PDC Roundup - Service Endpoint

Pingback from  WMOC#26 - Microsoft PDC Roundup - Service Endpoint

# Websites tagged "cil" on Postsaver

Thursday, July 16, 2009 3:02 PM by Websites tagged "cil" on Postsaver

Pingback from  Websites tagged "cil" on Postsaver

# Delegates and Optional Parameters : Philippe

Tuesday, August 25, 2009 3:24 AM by Delegates and Optional Parameters : Philippe

Pingback from  Delegates and Optional Parameters : Philippe

# Guidance for Using Optional and Named Parameters In C# & .NET 4.0 « Adam M Craven's Technical Blog

Pingback from  Guidance for Using Optional and Named Parameters In C# & .NET 4.0 «  Adam M Craven's Technical Blog