C# 4 introduced named and optional method parameters, which does a fine job of cleaning up method overloads. But a piece of the puzzle was left out. The params keyword could have been combined with named parameters to make it even stronger and address issues others have overcome by abusing other language features. To be fair, params works great with optional parameters. However, it is nearly unusable with named parameters.
Here’s an example of using params.
static void Main(string[] args)
{
WriteLine("{0} {1}", "Hello", "World!");
}
public static void WriteLine(string format, params object[] args)
{
Console.WriteLine(format, args);
}
Now let’s name the format parameter in the WriteLine call.
WriteLine(format: "{0}", "Hello World!");
You get the following error message:
Named argument specifications must appear after all fixed arguments have been specified
I believe this is a somewhat unfair message. The args parameter is an array of leftover parameters. The compiler could have been made intelligent enough to figure this out. There is only one way to use named parameters with params, and that is to name the parameter specified with the keyword.
WriteLine(format: "{0} {1} ", args: new[] {"Hello", "World!"});
This defeats the ease of use you gain with params, as the method works just as well without it.
Of more interest to me is utilizing parameter naming for a variable number of parameters. Imagine if the original code was written like this:
static void Main(string[] args)
{
WriteLine(format: "{hello} {world}", hello: "Hello", world: "World!");
}
public static void WriteLine(string format, params dict<string, object> args)
{
Console.WriteLine(format, args);
}
The code is a bit longer, but the format strings are more descriptive. Which is more understandable: {0}://{1}:{2}{3} or {protocol}://{host}:{port}{path}? I came up with this idea after noticing a reddit user was having an issue with the help for error message MSB3021. The message is “Unable to copy file "{0}" to file "{1}". {2}.” The description on MSDN didn’t tell you the meaning of each part… it’s really just a copy of the format string. You can infer that {0} is source and {0} is destination, but what is {2}? I hope it’s description, but it isn’t clear.
This would also solve the same problem the Lamba Abuse pattern solves, but it does so in a cleaner manner using a feature that fits more with its original intent.
For a quick refresher, here’s how the lambda abuse version of WriteLine would look, calling into the Hash class from MvcContrib. I added a method to Hash to encapsulate the functionality of instantiating Hash and replacing the format tags.
static void Main(string[] args)
{
WriteLine("{hello} {world}", hello => "Hello", world => "World!");
}
public static void WriteLine(string format, params Func<object, object>[] args)
{
Console.WriteLine(Hash.Format(format, args));
}
As Jeremy Skinner puts it, “By taking an array of delegates, it is possible to capture the compile-time variable names used by the lambda expressions and use them as keys for the dictionary.”
There are a couple of problems with doing this. Making the variable name of the lambda expression have implications runs counter to the standard implementation of lambda expressions. It is possible for someone to refactor your code and not realize that changing the variable name will break the the program’s functionality. Another problem is that every usage of this is tied to the Hash class of a third party library. It would be better if the desired functionality had actual language support.
One other thing I recommended in here was a type keyword, dict, for IDictionary. That’s only because I can imagine it would get long typing params IDictionary<object, object> every time.
Changes for this would require some compiler work, but I believe it would make the C# language stronger. There are more uses for it than formatting string: just about any time you need a dictionary you could use it. Hopefully this will make it into C# 5, but it only will if you ask the C# team for it. Blog about it, ask about it in the forums, demand and end to the lambda abuse!
In the meantime, if you want a solution for string formatting with named variables, check out James Newton-King’s article. He does it with anonymous types.