Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

A Format Control String Surprise

3.86/5 (3 votes)
12 Jun 2016CPOL3 min read 10.9K   73  
What happens when you have more items in your list than you have items in your control string?

Introduction

I have known for a long time that you must have enough items in your parameter array to account for the highest index assigned to a format item. Today's discovery was that unused items in the list are completely harmless.

Clearly, the opposite case is false; an item with an index greater than the upper bound of the parameter array elicits a FormatException exception.

Background

Format control strings are part of almost every .NET program, even the most trivial "Hello, World" program, because they are an implicit component of all but the most degenerate case of Console.Write() and Console.WriteLine(). Only the infrequently used overload that takes zero arguments works without one; for all others, it is a required argument. The same applies to all of its cousins, TextStrimg.WriteLine(), string.Format(), StringBuilder.AppendFormat(), and so forth.

This leads to the subject of the parameter array. If your list has three or fewer items, you can dispense with formally defining an array, and just list them as the second, third, and fourth arguments. Nevertheless, internally, they constitute an array, if only an implicit one.

Using the Code

A short C# console program demonstrates all of the above points fairly well. Following is the entire program, all 89 lines of it, 50 of them comments, and another 14 that are completely blank, leaving a mere 25 of actual code. Of those 25, four define constants, one imports the System namespace, and nine open or close scopes, leaving 12 to do something useful.

C#
using System;

namespace FormatItemCountDemo
{
    class Program
    {
        static void Main ( string [ ] pastrCmdLneArgs )
        {
            //    ----------------------------------------------------------------
            //    Format control strings defined as constants is my usual practice
            //    for internal programs. If I expect to distribute the program,
            //    these go into managed string resources. Having them defined as
            //    constants works well for this tutorial example.
            //    ----------------------------------------------------------------

            const string ANNOUNCE_1 = @"    Total Format Items in Array = {0}";
            const string ANNOUNCE_2 = @"{1}    Total Format Items in Array = {0}";
            const string ANNOUNCE_3 = @"{1}    Total Format Items in Array = {0}{2}";

            const string DUMMY_FORMAT_ITEM_2 = @" (Dummy Format Item 2, with leading space)";

            //    ----------------------------------------------------------------
            //    I think it's worth the computational cost to initialize, 
            //    even when the initial value is its default.
            //    ----------------------------------------------------------------

            int intNStrings = 0;

            Console.WriteLine ( "Begin format item counting demonstration.\r\n" );

            //    ----------------------------------------------------------------
            //    The first two writes succeed, because there are at least enough
            //    items in the parameter array to cover the highest index number
            //    that appears in the format control string.
            //    ----------------------------------------------------------------

            Console.WriteLine (
                ANNOUNCE_1 ,            // Format control string - one format item only, 
                                        // extra elemen6t in parameter array ignored
                ++intNStrings ,         // Format Item 0 = Counter, incremented before WriteLine sees it 
                Environment.NewLine );  // Format Item 1 = Embedded newline, 
                                        // unused because only one format item in format control string
            Console.WriteLine (
                ANNOUNCE_2 ,            // Format control string
                ++intNStrings ,         // Format Item 0 = Counter, incremented before WriteLine sees it 
                Environment.NewLine );  // Format Item 1 = Embedded newline, 
                                        // manifests as blank line above message

            //    ----------------------------------------------------------------
            //    The third write fails because it uses the same parameter array,
            //    but the format control string has a format item with an index of
            //    2, which exceeds the highest index available from the array.
            //    ----------------------------------------------------------------

            try
            {
                Console.WriteLine (
                    ANNOUNCE_3 ,             // Format control string - Format items account 
                                             // for all items in array, so all are consumed
                    ++intNStrings ,          // Format Item 0 = Counter, 
                                             // incremented before WriteLine sees it 
                    Environment.NewLine );   // Format Item 1 = Embedded newline, 
                                             // manifests as blank line above message
            }
            catch ( FormatException errBadFormatControlString )
            {
                Console.WriteLine (
                    "{2}{0} exception{2}{2}    Exception Message: {1}" , // Format control string - 
                                                                     // causes FormatException because 
                                                                     // highest format item index is 2, 
                                                                     // but highest subscript 
                                                                     // in parameter array is 1
                    errBadFormatControlString.GetType ( ).FullName , // Format Item 0 = Full Name 
                                                                     // of Exception type
                    errBadFormatControlString.Message ,              // Format Item 1 = Message 
                                                                     // from Exception
                    Environment.NewLine );                           // Format Item 2 = Embedded 
                                                                     // Newline
            }

            //    ----------------------------------------------------------------
            //    Repeating with the same format control string and an extra item
            //    in the parameter array allows the formatted write to succeed.
            //    ----------------------------------------------------------------

            Console.WriteLine (
                ANNOUNCE_3 ,            // Format control string - Format items 
                                        // account for all items in array, so all are consumed
                intNStrings ,           // Format Item 0 = Counter, incremented before WriteLine sees it 
                Environment.NewLine ,   // Format Item 1 = Embedded newline, 
                                        // manifests as blank line above message
                DUMMY_FORMAT_ITEM_2 );  // Format Item 2 = Dummy format item, to make the statement work

            //    ----------------------------------------------------------------
            //    Careful attention to numbering items in the format array reduces
            //    the risk of format exceptions, though the method is not entirely
            //    foolproof.
            //    ----------------------------------------------------------------

            Console.WriteLine (
                "{0}Done!{0}" ,           // Format control string
                Environment.NewLine );    // Format item 0 = Embedded Newline 
                                          // manifests as blank line above and below message
        }    // static void Main
    }    // class Program
}    // namespace FormatItemCountDemo

Create a new Console Program project in Visual Studio, paste everything shown above into it, and build. Set a breakpoint on the closing brace of the main routine if you want to see anything when it runs in the interactive debugger. Alternatively, after you build it, you can open a command prompt in the output directory, and execute the program, as I did to create the picture shown below.

Output of Demo, run in CMD.EXE

Points of Interest

Most of the interesting bits are covered in the embedded comments. In case you aren't paying attention, the messages indicate the number of format items in the array; what they really convey is the minumum number of format items the array must contain to satisfy the format control string.

Apart from the discovery that motivated this article, one feature of the code that deserves attention is the way I comment the parameter arrays whenever I use Console,WriteLinestring.Format, and others that format strings.

Since the first two lines are printed from the same array, it is evident that the unused second item is completely ignored. Apart from wasting a few bytes, extra items in the array are completely harmless.

The third print statement is wrapped in a try/catch block, to prevent an unhandled exception from crashing the program. Following the exception report generated when the third print statement tries to execute, the fourth statement uses the same format control string, but it adds another item to the array of items. With three items, the array covers the format items in the format control string, and the print succeeds.

That's about all there is to it, apart from the fact that paring down the list of references to the bare essentials (System), decreases the number of references from eight to three. The demonstration package is a complete Visual Studio solution, including both debug and release builds. As built, the assembly targets version 4.5 of the Microsoft .NET Framework, though it could certainly target any version of the framework, since it can hardly be said to push the envelope of Console.WriteLine, which has been part of the framework since day 1.

History

  • Sunday, 12th June 2016 saw the discovery of this quirk, creation of the demonstration program, and writing of this article
  • Monday, 13th June 2016, I discovered and corrected two typographical errors.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)