Click here to Skip to main content
12,824,299 members (43,632 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as


29 bookmarked
Posted 19 Jul 2013

Extended string.Format()

, 19 Jul 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
Here I describe how you can write your own implementation of string.Format method using slightly more readable syntax of format string.


This article describes how new version of string.Format() method could be implemented with new more readable syntax of format string.  


Personally I like string.Format (or StringBuilder.AppendFormat) very much. I use it frequently and think that it is great if there are not too many arguments in your format string. But if it is not the case things look not so bright.

Lets consider the following code generating some SQL query:

var sql = string.Format("SELECT {0} FROM [{1}].[{2}].[{3}] INNER JOIN [{1}].[{2}].[{4}]{5}{6}",

For me it looks little messy. It can take some time to understand what corresponds to e.g. the argument #5. But worse thing happens if I need to change the query and add some new argument in the beginning of string. E.g. I'd like to add "TOP" expression. I can do it like this:

var sql = string.Format("SELECT {7}{0} FROM [{1}].[{2}].[{3}] INNER JOIN [{1}].[{2}].[{4}]{5}{6}",

Now I have argument #7 before argument #0 in my format string. It looks ugly for me. Another approach is to enumerate all arguments, but it is very error prone.

What I want to have is something like this: 

var sql = StringEx.Format("SELECT {TopClause}{Columns} FROM [" + 
  "{Database}].[{Schema}].[{Table1}] INNER JOIN [{Database}]." + 
    new {
        TopClause = GetTopClause(),
        Columns = GetColumns(),
        Database = GetDatabaseName(),
        Schema = GetSchemaName(),
        Table1 = GetFirstTable(),
        Table2 = GetSecondTable(),
        WhereClause = GetWhereClause(),
        GroupByClause = GetGroupByClause()

Let's see how we can do it. 

Using the code 

The basic idea behind the code is simple. I change format string in new format into format string in old format. E.g. something like "{Value} and {Score} or {Value}" I replace with "{0} and {1} or {0}". While doing it one should remember 2 things: 

  1. I should not process format items with double curly brackets: "{{Value}}" 
  2. I should preserve formatting components. It means that strings like "{Value,5:D3}" should be converted into "{0,5:D3}"

Here is the method converting new format into old format:

public ConvertedFormat Convert(string format)
    var placeholders = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
    var regex = new Regex("{[^{}]+}");
    StringBuilder formatBuilder = new StringBuilder(format);
    foreach (var match in regex.Matches(format).OfType<Match>().OrderByDescending(m => m.Index))
        if (!ShouldBeReplaced(formatBuilder, match))
        { continue; }
        var memberInfo = GetMemberInfo(match);
        if (!placeholders.ContainsKey(memberInfo.MemberName))
            placeholders[memberInfo.MemberName] = placeholders.Count;
        var memberIndex = placeholders[memberInfo.MemberName];
        formatBuilder.Replace(match.Value, string.Format("{{{0}{1}}}", 
           memberIndex, memberInfo.Formatting), match.Index, match.Length);
    var convertedFormat = new ConvertedFormat(formatBuilder.ToString(), 
        placeholders.OrderBy(p => p.Value).Select(p => p.Key).ToArray());
    return convertedFormat;

First of all I find all possible candidates for replacement using regular expression "{[^{}]+}" (it means "something in curly brackets"). I replace them in the initial format string with new format items. To keep correct positions of unprocessed candidates I use OrderByDescending to replace candidates from the end to the beginning. Then method ShouldBeReplaced checks if this is a valid candidate for replacement ("{Value}" not "{{Value}}"). Then method GetMemberInfo extracts from format item with all components ("{Value,5:D3}") name component ("Value") and other components (",5:D3"). After this I check if I already have format item with this name. For this purpose I use dictionary placeholders where for each name component of format items I store position in the array of argument I'll send to string.Format later. And final step is the replacement of candidate itself.

In the end I have format string in old format and array of names of members of my data object.  It is very easy to extract values of these members using Reflection.

One more point of interest is how I determine if a candidate is valid for replacement. For example in the following texts "{Value}" must be replaced: "{Value}", "{{{Value}}}", "{{{{{Value}}}". And in the following must not: "{{Value}}", "{{{{Value}}". Here is the code solving this problem:

private static bool ShouldBeReplaced(StringBuilder formatBuilder, Match match)
    var bracketsBefore = 0;
    var index = match.Index - 1;
    while (index >= 0 && formatBuilder[index] == '{')

    return ((bracketsBefore % 2) == 0);

I just count number of curly brackets before format item.

Points of Interest

Although my code only creates new Format method which can be used instead of string.Format, you can easily write same method for StringBuilder class. You may create it in form of extension method to be more convenient.


  • Initial revision.


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


About the Author

Ivan Yakimov
Software Developer (Senior) Confirmit
Russian Federation Russian Federation
No Biography provided

You may also be interested in...

Comments and Discussions

GeneralMy vote of 5 Pin
StanleyJHubert5-Feb-15 23:32
memberStanleyJHubert5-Feb-15 23:32 
GeneralMy vote of 5 Pin
Halil ibrahim Kalkan18-Aug-13 20:19
memberHalil ibrahim Kalkan18-Aug-13 20:19 
GeneralMy vote of 5 Pin
Forogar16-Aug-13 8:19
memberForogar16-Aug-13 8:19 
GeneralMy vote of 5 Pin
JVH_MCS30-Jul-13 9:18
memberJVH_MCS30-Jul-13 9:18 
QuestionIt's changing awl on soup. Pin
Thornik25-Jul-13 4:32
memberThornik25-Jul-13 4:32 
QuestionInteresting, but... Pin
Marc Clifton23-Jul-13 7:04
protectorMarc Clifton23-Jul-13 7:04 
GeneralMy vote of 5 Pin
VitorHugoGarcia22-Jul-13 11:00
memberVitorHugoGarcia22-Jul-13 11:00 
SuggestionNice idea but... Pin
mrchief_200022-Jul-13 8:34
membermrchief_200022-Jul-13 8:34 
GeneralMy vote of 5 Pin
kosmoh19-Jul-13 8:38
memberkosmoh19-Jul-13 8:38 
GeneralMy vote of 5 Pin
fredatcodeproject19-Jul-13 8:06
memberfredatcodeproject19-Jul-13 8:06 
GeneralMy vote of 5 Pin
H. Mueller19-Jul-13 6:52
professionalH. Mueller19-Jul-13 6:52 
GeneralVery Interesting - I like it !! Pin
cvogt6145719-Jul-13 6:47
membercvogt6145719-Jul-13 6:47 
QuestionBroken/incompatible solution. Pin
Leslie Nielsen 15157619-Jul-13 6:13
memberLeslie Nielsen 15157619-Jul-13 6:13 
AnswerRe: Broken/incompatible solution. Pin
Ivan Yakimov21-Jul-13 21:35
memberIvan Yakimov21-Jul-13 21:35 
GeneralRe: Broken/incompatible solution. Pin
Leslie Nielsen 15157621-Jul-13 22:46
memberLeslie Nielsen 15157621-Jul-13 22:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170308.1 | Last Updated 19 Jul 2013
Article Copyright 2013 by Ivan Yakimov
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid