Optional named parameters work pretty well

Not exactly ducks Rob has found a use for dynamic:
http://blog.wekeroad.com/2010/08/06/flexible-parameters-with-csharp

Yay! Let’s celebrate!

Well, I was a little puzzled because I don’t think it quite adds up in the specific example he chose (although please see no aggressiveness here: Rob’s a friend; peace!). The idea is to have the same flexibility that a dynamic language can offer in terms of evolution of an API. Here’s his original Ruby example:

def my_method(args)
  thing_one = args["thing1"]
  thing_two = args["thing2"]
end

my_method :thing1 => "value", :thing2 = Time.now

The idea, which is quite common in dynamic languages, is that instead of passing a list of predetermined parameters in a specific order, you pass in a dictionary of sorts that contains a list of named parameters in no particular order. It’s more readable (think in particular of the inexpressive mess that C# is when you have a few Boolean parameters on a method) and it’s more flexible: if you need to accept a new option or parameter, just do it and existing code won’t break. Your code can just assume a default value for the new option and that’s it.

Which is something that C# 4.0 does quite well with named optional parameters. Here was Rob’s solution by the way, which is quite smart:

static void DoStuff(dynamic args) {
    Console.WriteLine(args.Message);
}

DoStuff(new { Message = "Hello Monkey" });

And here’s the same thing expressed with optional parameters:

static void DoStuff(string message = null) {
    Console.WriteLine(message);
}

DoStuff(message: "Hello Monkey" });

In terms of readability and flexibility, both approaches are roughly equivalent, but the optional parameters are providing the opportunity for IntelliSense (for both code inside the method and client code) as well as static compile-time type checking. If you want to add an additional parameter, that’s really easy, just add it. If the calling code relies on named parameters, it will still work as the value for the newly added parameter will be assumed to be the default.

Or will it? Actually, not exactly, this will require a recompilation of the client app, which is a FAIL of sorts. The reason for that is that optional parameters really are mostly a compilation trick so if you change the signature, even though the client code doesn’t need  to change, the compiled version of it does as it needs to bind to a different method, with a different signature.

But on expressiveness and flexibility criteria only, I would pick optional parameters instead of the dynamic approach every time.

Now don’t get me wrong, an anonymous object as an options parameter still has its place when the method it’s being passed to is going to pass down the contents of the objects without caring about its precise structure or contents.

An example of that, also taken from Rob’s post, is an HTML helper that takes an option object that gets transformed into attributes of the tag being rendered:

Html.TextBox("name", "value", new {size = 20});

Here, the whole point is to enable custom attributes that the API author did not think about in advance and let those flow into the markup. The code for the helper doesn’t expect anything in particular from this object, it just copies properties over. I think an option object in this case is fine.

Although one can’t help but wish there were some super-easy JSON-like syntactic sugar in C# to create dictionaries, inferring the key and value types at compile-time, something like that:

var a = new {
    foo = "bar",
    bar = "baz",
    answer = 42             
};

but that wouldn't require reflection or dynamic.

On the other hand the HTML helper case is also a case where the use of dynamic does not bring a lot of value unless I’m missing something…

UPDATE: Phil explains in details why changing the signature requires a recompilation of the client code: http://haacked.com/archive/2010/08/10/versioning-issues-with-optional-arguments.aspx.

1 Comment

  • Might be worth mentioning the 'middle ground' approach, which is to take a strongly typed 'settings' object. It has some positives from both approaches you cover:
    - It's strongly typed (like default params)
    - You don't need to recompile callers if you add params (like with dynamic)

    Obviously, it's not perfect either, as it requires defining a new class, and the syntax is not as nice as with default params.

    But it belongs in this discussion :)

Comments have been disabled for this content.