Sunday, February 22, 2009

Now Is The Winter of Our Optional and Named Parameters

Optional and named method parameters are coming to C# 4. This means you can provide default values for method parameters. When you call the method, you can name only the parameters you want to specify - default values will be used for all other parameters.

Here's how you use it:

public void Foo (
    int doodad = 1,
    string humdinger = "",
    string wuchacallit = "STELLAAAA!")
{
    // Do stuff here
}

void Test ()
{
    Foo ();
    Foo (humdinger = "Beard Lust");
    Foo (doodad = 5,
         wuchacallit = "SHAMU!");
    Foo (wuchacallit = "Kia",
         humdinger = "Ora",
         doodad = 42);
}

Here's how it really works:

public void Foo (
    [Optional, DefaultParameterValue(1)]
    int doodad,
    [Optional, DefaultParameterValue("")]
    string humdinger,
    [Optional, DefaultParameterValue("STELLAAAA!")]
    string wuchacallit)
{
    // Do stuff here
}

void Test ()
{
    Foo (1, "", "STELLAAAA!");
    Foo (1, "Beard Lust", "STELLAAAA!");
    Foo (5, "", "SHAMU!");
    Foo (42, "Ora", "Kia");
}


The compiler just sprinkles the default values into every callsite. There are a few problems with this approach. Let's suppose I release version 1 of my awesome library with the above Foo method. You compile. All is well. Now let's say I release version 2 of my library, in which the default value "STELLAAAA!" is changed to "KHAAAAN!". But your code still has the old default value baked in. You need to re-compile your code to get the new default value. There is also the problem that injecting the full argument list into every callsite bloats the size of the code. Bigger code means more to JIT and fewer cache hits

How it should work:

struct FooSettings {
    int doodad_value = 1;
    string humdinger_value = "";
    string wuchacallit_value = "STELLAAAA!";

    public int doodad {
        get { return doodad_value; }
        set { doodad_value = value; }
    }
    public string humdinger {
        get { return humdinger_value; }
        set { humdinger_value = value; }
    }
    public string wuchacallit {
        get { return wuchacallit_value; }
        set { wuchacallit_value = value; }
    }
}

public void Foo (FooSettings settings)
{
    // Do stuff
}

void Test ()
{
    Foo (new FooSettings ());
    Foo (new FooSettings { humdinger = "Beard Lust" });
    Foo (new FooSettings {
        doodad = 5,
        wuchacallit = "SHAMU!" });
    Foo (new FooSettings {
        wuchacallit = "Kia",
        humdinger = "Ora",
        doodad = 42 });
}


Thanks to C# 3's object initialization, you can squint at the call and almost see the named parameter syntax (just ignore "new FooSettings"). This pattern of using a special "settings" type for passing arguments to methods already exists in the framework (see XmlReader.Create and XmlWriter.Create for an example). I am proposing that the compiler auto-generate these types and provide full optional/named parameter sugar. The compiler-generated types would be publicly nested within the type containing the method and named "[MemberName]Settings" by default.

This is better than callsite default value injection because:
  • It versions well

  • It adds a fixed amount of additional code (the type), whereas injection adds more code every time you use it

  • It is CLS compliant

This is how C# 4 should do optional and named parameters.