String.Format count the number of expected args

I think this will handle Escape brackets and 0:0000 stuff... will give the greatest bracketed value... so in my example will give 1.

       //error prone to malformed brackets...
        string s = "Hello {0:C} Bye {1} {0} {{34}}";

        int param = -1;
        string[] vals = s.Replace("{{", "").Replace("}}", "").Split("{}".ToCharArray());
        for (int x = 1; x < vals.Length-1; x += 2)
        {
            int thisparam;
            if (Int32.TryParse(vals[x].Split(',')[0].Split(':')[0], out thisparam) && param < thisparam)
                param = thisparam;
        }
        //param will be set to the greatest param now.

You could use a regex, something like {(.*?)} and then just count the matches. If you need to handle cases like {0} {0} (which I guess should return 1) then that makes it a little more difficult, but you could always put all of the matches in a list an do a Linq select distinct on it. I'm thinking something like the code below:

var input = "{0} and {1} and {0} and {2:MM-dd-yyyy}";
var pattern = @"{(.*?)}";
var matches = Regex.Matches(input, pattern);
var totalMatchCount = matches.Count;
var uniqueMatchCount = matches.OfType<Match>().Select(m => m.Value).Distinct().Count();
Console.WriteLine("Total matches: {0}", totalMatchCount);
Console.WriteLine("Unique matches: {0}", uniqueMatchCount);

EDIT:

I wanted to address some of the concerns raised in the comments. The updated code posted below handles the cases where there are escaped bracket sequences (i.e., {{5}}), where no parameters are specified, and also returns the value of the highest parameter + 1. The code assumes that the input strings will be well formed, but that trade off may be acceptable in some cases. For example, if you know that the input strings are defined in an application and not generated by user input, then handling all of the edge cases may not be necessary. It might also be possible to test all of the error messages to be generated using a unit test. The thing I like about this solution is that it will most likely handle the vast majority of the strings that are thrown at it, and it is a simpler solution than the one identified here (which suggests a reimplementation of string.AppendFormat). I would account for the fact that this code might not handle all edge cases by using a try-catch and just returning "Invalid error message template" or something to that effect.

One possible improvement for the code below would be to update the regex to not return the leading "{" characters. That would eliminate the need for the Replace("{", string.Empty). Again, this code might not be ideal in all cases, but I feel it adequately addresses the question as asked.

const string input = "{0} and {1} and {0} and {4} {{5}} and {{{6:MM-dd-yyyy}}} and {{{{7:#,##0}}}} and {{{{{8}}}}}";
//const string input = "no parameters";
const string pattern = @"(?<!\{)(?>\{\{)*\{\d(.*?)";
var matches = Regex.Matches(input, pattern);
var totalMatchCount = matches.Count;
var uniqueMatchCount = matches.OfType<Match>().Select(m => m.Value).Distinct().Count();
var parameterMatchCount = (uniqueMatchCount == 0) ? 0 : matches.OfType<Match>().Select(m => m.Value).Distinct().Select(m => int.Parse(m.Replace("{", string.Empty))).Max() + 1;
Console.WriteLine("Total matches: {0}", totalMatchCount);
Console.WriteLine("Unique matches: {0}", uniqueMatchCount);
Console.WriteLine("Parameter matches: {0}", parameterMatchCount);

Tags:

C#

.Net

String