We looked at the ToString method very briefly in the previous solution. Considering that it can leverage standard .NET string formatting, you may well be tempted to use ToString in conjunction with the concatenation operator (+), as I’ve done in the following example:
“Date is ” + DateTime.Now.ToString(“MM/dd hh:mm:ss”);
Here, I’m using ToString to specify the format of the DateTime object. It might be intuitive, but it’s not the best solution. A much more efficient approach that produces identical output is to call the String.Format method on that DateTime object, as I’ve done here:
String.Format(“Date is {0:MM/dd hh:mm:ss}”, DateTime.Now);
This is reminiscent of one of the classic uses for a string that dates back to the days of C and sprintf—for specifying an output format. String formatting is incredibly powerful, but it doesn’t have to be complicated—in fact most of the time you’ll find yourself performing only simple types of string concatenation.
Here’s another example:
d.SelectSingleNode(“/a/b[value='" + value + "']“);
While this approach works, it gives us a lot of plus signs and broken string fragments to keep track of. You may forget to close the quotes, or you might lose track of the number of brackets because they’re separated by multiple plus signs. Moreover, should you use this code often, there may be serious implications for the performance of your application.
Let’s replace that concatenation with a single String.Format command:
d.SelectSingleNode(String.Format(“/a/b[value='{0}']“, value));
We now have one unbroken string with a simple replacement operation. It’s unambiguous, and it performs well.
Some people feel so strongly about using String.Format that they vow never to use + to concatenate strings ever again. I don’t feel quite that strongly about it—I believe that concatenation has its place for very simple tasks. But you should definitely use String.Format whenever possible, for these reasons:
*
Your code will be cleaner.
*
You’ll avoid potential concatenation performance problems.
*
Using String.Format is a far more powerful approach than concatenation.
Note: String Concatenation Versus String Builder
For more information about the performance implications of string concatenation, see the MSDN article titled Improving String Handling Performance in .NET Framework Applications.
How Powerful is String.Format?
We’ve only encountered the very simplest string formatting option so far, which is quite basic—direct, numbered replacement:
String.Format(“I like {1}, {0}, and {2}”, “ninjas”, “pirates”,
“cowboys”);
In the example above, the first variable replaces the {0}, the second variable replaces the {1}, and so forth. This code may be easy to understand, but it’s not particularly exciting. Let’s add some features to make it more compelling.
We’ll start by adding the Format identifier to specify alignment. A positive value indicates that the string should be right-justified, while a negative means it should be left-justified. The value specifies the total length that the resulting string should take when padded with spaces:
String.Format(“{0,-10}”, “left”); // “left “
String.Format(“{0, 10}”, “right”); // ” right”
Another common way to use the format string—the characters to the right of the colon—is to specify the formatting of numbers, dates, and enumerations:
String.Format(“{0,-8:G2}”, 3.14159); // “3.1 “
Here’s where the real power of String.Format reveals itself. String.Format has a number of built-in number formatting specifiers, which are shown in the code listing below. Note that each specifier has a relatively easy-to-remember, case-insensitive, single-letter mnemonic associated with it: d for decimal, x for hexadecimal, and so forth:[2]
int i = 32768;
String.Format(“{0:c}”, i); // $32,768 (currency)
String.Format(“{0:d}”, i); // 32768 (decimal)
String.Format(“{0:e}”, i); // 3.276800e+004 (scientific notation)
String.Format(“{0:f}”, i); // 32768.00 (fixed-point)
String.Format(“{0:g}”, i); // 32768 (general)
String.Format(“{0:n}”, i); // 32,768.00 (number with commas)
String.Format(“{0:p}”, i); // 32,768% (percent)
String.Format(“{0:r}”, i); // 32768 (round trip)
String.Format(“{0:x}”, i); // 8000 (hexadecimal)
We can add a digit to some of the built-in numeric format specifiers to indicate how many decimal places we want the output to display; however, d and x cannot take a numeric format specifier because they require the number to be an integer:
String.Format(“{0:c3}”, i); // $32,768.000
String.Format(“{0:c2}”, i); // $32,768.00
In addition to the pre-built number formatting specifiers, ASP.NET permits the use of custom number formatters. A complete description of all the formatters is beyond the scope of this book, but here are a few examples to give you a glimpse of the possibilities:
double d = 1234.56;
String.Format(“{0:00.0000}”, d); // 1234.5600 (zero placeholder)
String.Format(“{0:(#).##}”, d); // (1234).56 (digit placeholder)
String.Format(“{0:0.0}”, d); // 1234.6 (decimal point)
String.Format(“{0:0,0}”, d); // 1,235 (thousands)
String.Format(“{0:0,.}”, d); // 1 (number scaling)
String.Format(“{0:0%}”, d); // 123456% (percent)
String.Format(“{0:00e+0}”, d); // 12e+2 (scientific notation)
These are some of the more common combinations that are available; you can view a more detailed list on the MSDN site.
But what about dates and times? Let’s see one of the built-in date formatters in action:
String.Format(“{0:g}”, DateTime.Now);
This outputs the current date and time in the following format:
8/05/2007 11:13 AM
There’s a plethora of ways in which a date and time can be formatted, as shown by the following examples (note that I’ve omitted the call to String.format for brevity). These date formatters also come with single-letter mnemonics, although they’re perhaps not quite as intuitive as those used to format numbers. The default date format is the general format used in the example above. Formats include:
“{0:d}” // 8/21/2007 (short date)
“{0:D}” // Tuesday, 21 August 2007 (long Date)
“{0:f}” // Tuesday, 21 August 2007 11:13 AM (full short)
“{0:F}” // Tuesday, 21 August 2007 11:13:17 AM (Full long)
“{0:g}” // 21/08/2007 11:13 AM (general)
“{0:G}” // 21/08/2007 11:13:17 AM (General long)
“{0:m}” // 21 August (month day)
“{0:o}” // 2007-08-21T11:13:17.4687500+10:00 (round trip)
“{0:R}” // Tue, 21 August 2007 11:13:17 GMT (RFC1123 pattern)
“{0:s}” // 2007-08-21T11:13:17 (sortable)
“{0:t}” // 11:13 AM (short time)
“{0:T}” // 11:13:17 AM (long Time)
“{0:u}” // 2007-08-21 11:13:17Z (universal)
“{0:U}” // Tuesday, 21 August 2007 1:13:17 AM (Universal GMT)
“{0:Y}” // August 2007 (Year month)
Whew, that’s quite a list! If you’re still not quite satisfied with any of the predefined date and time format specifiers, you can use the custom date formats to create your own. Here are a few examples:
“{0:dd}” // 06 (day)
“{0:ddd}” // Sat (day abbr)
“{0:dddd}” // Saturday (day full)
“{0:fff}” // 692 (second fraction)
“{0:gg}” // A.D. (era)
“{0:hh}” // 07 (12 hour)
“{0:HH}” // 19 (24 hour)
“{0:mm}” // 21 (minute)
“{0:MM}” // 01 (month)
“{0:MMM}” // Jan (month abbr)
“{0:MMMM}” // January (month full)
“{0:ss}” // 29 (seconds)
“{0:tt}” // PM (am/pm)
“{0:yy}” // 07 (year)
“{0:yyyy}” // 2007 (year full)
“{0:zz}” // -08 (timezone)
“{0:zzz}” // -08:00 (timezone full)
“{0:hh:mm:ss}” // 07:21:29 (separators)
“{0:MM/dd/yyyy}” // 01/06/2007 (separators)
Tip: Months and Minutes
Watch out for the minutes and month mnemonic (go on, say that three times fast—I dare you!). The standard, single-letter formatter (m or M) is case-insensitive and means month. However, when you begin specifying your own custom format string (using multiple letters, such as mm or MMM), you’ll soon discover that case does matter.
For custom format strings, a lowercase m means minutes, and an uppercase M means month. Here’s how I remember that: months are “larger” than minutes.
Warning: Dates are Culture-sensitive
There is one very important caveat to keep in mind whenever you’re working with dates. All date and time output is heavily dependent on the system’s current regional settings. Don’t assume that because you live in the Eastern Time Zone the names you use for months will be identical to those used by a person living in Kazakhstan, for example. If you’re worried about culture-independent date display, use the overloaded version of String.Format that accepts a culture:
String.Format(
System.Globalization.CultureInfo.InvariantCulture,
“{0:d}”, d
)
If you pass in InvariantCulture, you’re guaranteed that the date and time output will be universally understood, no matter where your code happens to be running in the world. Tim Berners-Lee would be proud of you for putting the “world” back into World Wide Web!
Discussion
If you need to use a reserved character in a formatting string, you can surround it with single quotes to escape it; your character will show up verbatim in the output. In the example below, I’ve escaped the percentage symbol:
String.Format(“{0:##.00′%’”, 1.23) // “1.23%”
If you still doubt the power of string formatting, consider this little nugget:
int i = 1;
String.Format(“{0:yes;;no}”, i);
The output of this line of code depends on the value of the variable i—a value of zero outputs no, and a non-zero value outputs yes. What we have here is an example of conditional formatting—an output that is conditional on the value of the variable passed in. Conditional formats take the following form:
String.Format(“{0:positive;negative;zero}”, variable);
As you can see, the three possible outputs of a conditionally formatted string are separated by semicolons. If variable has a value that is positive, the first string following the colon is displayed (in this case, positive); if variable is negative, the second string (negative) is displayed; and if variable has a value of 0, the third string (zero) is displayed.
Here’s another example—one that’s used quite often:
String.Format(“{0:$#,##0.00;($#,##0.00);Zero}”, d);
This conditional format string follows the accountant’s convention of placing negative values in parentheses, and replaces the value 0 with the string Zero.
As you can see from the large number of examples listed in this solution, ASP.NET contains two extremely powerful tools for formatting strings: String.Format and, to a lesser degree, ToString. Yet these methods are just two small parts of the String class.
Strings, I’ve fallen in love with you all over again.
[2]Complete descriptions of each of the standard numeric format strings can be found in the online MSDN documentation: http://msdn2.microsoft.com/en-us/library/dwhawy9k.aspx.