views:

157

answers:

3

Before using String.Format to format a string in C#, I would like to know how many parameters does that string accept?

For eg. if the string was "{0} is not the same as {1}", I would like to know that this string accepts two parameters For eg. if the string was "{0} is not the same as {1} and {2}", the string accepts 3 parameters

How can I find this efficiently?

+1  A: 

You'll have to parse through the string and find the highest integer value between the {}'s...then add one.

...or count the number of sets of {}'s.

Either way, it's ugly. I'd be interested to know why you need to be able to figure out this number programatically.

EDIT

As 280Z28 mentioned, you'll have to account for the various idiosyncrasies of what can be included between the {}'s (multiple {}'s, formatting strings, etc.).

Justin Niessner
But remember that `{{Tricky}}` can appear, as can `{0:0000}`.
280Z28
so, why do I need this? I am working with OpenXML documents where the xpath queries are placed inside custom xml part of word docs. In my code, I need to replace the parameters within the xpath query with values based on the context of that xpath. The number of parameters within the different xpaths in the document will be varying so if I can find the number of parameters the string accepts, I can use an if-else based on that to perform the correct String.FormatI hope I explained this clearly.
Pratik Kothari
Basically, you need to implement yourself the first half of the `StringBuilder.AppendFormat(IFormatProvider provider, string format, params object[] args)` method implementation (~ 75 lines of code) to handle all the idiosyncrasies.
Igor Korkhov
+5  A: 

String.Format receives a string argument with format value, and an params object[] array, which can deal with an arbitrary large value items.

For every object value, it's .ToString() method will be called to resolve that format pattern

EDIT: Seems I misread your question. If you want to know how many arguments are required to your format, you can discover that by using a regular expression:

string pattern = "{0} {1:00} {{2}}, Failure: {0}{{{1}}}, Failure: {0} ({0})";
int count = Regex.Matches(Regex.Replace(pattern, 
    @"(\{{2}|\}{2})", ""), // removes escaped curly brackets
    @"\{\d+(?:\:?[^}]*)\}").Count; // returns 6

As Benjamin noted in comments, maybe you do need to know number of different references. If you don't using Linq, here you go:

int count = Regex.Matches(Regex.Replace(pattern, 
    @"(\{{2}|\}{2})", ""), // removes escaped curly brackets
    @"\{(\d+)(?:\:?[^}]*)\}").OfType<Match>()
    .SelectMany(match => match.Groups.OfType<Group>().Skip(1))
    .Select(index => Int32.Parse(index.Value))
    .Max() + 1; // returns 2

This also address @280Z28 last problem spotted.

Edit by 280Z28: This will not validate the input, but for any valid input will give the correct answer:

int count2 =
    Regex.Matches(
        pattern.Replace("{{", string.Empty),
        @"\{(\d+)")
    .OfType<Match>()
    .Select(match => int.Parse(match.Groups[1].Value))
    .Union(Enumerable.Repeat(-1, 1))
    .Max() + 1;
Rubens Farias
Yours doesn't catch `"Failure: {0}{{{1}}}"` or `"Failure: {0} ({0})"`.
280Z28
And the OP wants to know the number of objects to pass to the call, afaik - so he doesn't need the number of references but the number of different references. "{0} {0} {1}" probably would count as "2" in his world. This is very easy from your expression though. Nice.
Benjamin Podszun
@Rubens: Yep, you got the first of them but not the second. You have to parse the matches an an integer and take the max plus 1. Here's another valid one: `"{2}"` (three parameters, but only one substitution).
280Z28
@280Z28, here you go again; please, let me go back to work and stop testing =)
Rubens Farias
@Benjamin, I updated that answer based on your feedback, but removed `Distinct().Count()` to add `Max() + 1` per @280Z28 demand.
Rubens Farias
@Rubens: Since `Format` throws a FormatException for the (unclosed) string `"{0"`, you can simplify your match expression to just `@"\{(\d+)"`.
280Z28
Since `Max` throws for an empty input, I added the union with a single -1 entry, returning 0 as the parameter count for strings without parameters. `string.Replace` has better performance than `Regex.Replace`, and since you know there is exactly 1 relevant group for each match you can use Select instead of SelectMany.
280Z28
@280Z28, loved that edit, ty
Rubens Farias
A: 

I rely on ReSharper to analyze that for me, and it is a pity that Visual Studio does not ship with such a neat feature.

Lex Li