Friday 18 March 2011

C# String Format Run Time Field Width

Explanation As Promised

String formatting in C# is no small subject. With so many variations, its documentation can seem all over the place in MSDN, making it a bit of a pain to track down that one spec detail you need. And occasionally, a lot of a pain, when it transpires the feature you're chasing doesn't exist, never has, nor will :-( Example: what's the C# equivalent of this Delphi fragment, which formats a given string value into a field whose width is determined at run time:
ShowMessage(Format('%*s', [width, value]));
The key point here is that '*' character in the middle of the format string, consuming one numerical value from the input data, and assigning it to the field width expected in that position. Frustratingly, the answer is that the standard C# string formatting arrangements have no such feature. However, having said that, the following C# code does display any given value (not just strings), right justified, in a field of the specified width:
Console.Write(string.Format("{{0,{0}}}", width), value);
What's all that about then? There are two "tricks" at work in combination here, and they both have to do with hiding something. Firstly, the particular overload of Console.Write being used accepts a format string as its first parameter. But this format string itself is what's being computed in the string.Format expression. Without that overload, we would instead have to use something like this equivalent (effectively what the compiler does):
Console.Write(string.Format(string.Format("{{0,{0}}}", width), value));
For clarity, that repetition of string.Format is not a typo. This is in turn roughly equivalent to:
var format = string.Format("{{0,{0}}}", width);
Console.Write(string.Format(format, value));
The second trick is the doubling up of the curly braces in the string constant, "escaping" them in the context of the enclosing string.Format function. Rather than performing straightforward substitution inside "{{...}}", the function instead just removes one level of braces. However, the interior "{0}" itself, being unescaped, is replaced as normal by the number width. For illustration, suppose width is 6. Then after application of the first string.Format, we will have this:
var format = "{0,6}";
Console.Write(string.Format(format, value));
Finally, getting rid of our temporarily introduced format variable, this is equivalent to:
Console.Write(string.Format("{0,6}", value));
So there's no magic format string feature for run time specification of field widths. All we've done is create a particular format string instance at run time, one which happens to contain the desired field width as a string. Simples ;-)

Incidentally, this technique crops up incessantly whenever you're metaprogramming - writing code which, at run time, writes code. At the heart of a SQL Builder or an IQueryable implementation for example, you might find you have to brace yourself with three or even more nested levels of those things.

No comments:

Post a Comment