String.Format подсчитывает количество ожидаемых аргументов

Можно ли подсчитать количество ожидаемых аргументов /params в строке для String.Format()?

Например: "Hello {0}. Bye {1}" должно возвращать счетчик 2.

Мне нужно показать ошибку до того, как String.Format() выдает исключение.

Спасибо за вашу помощь.

Ответ 1

Вы можете использовать регулярное выражение, что-то вроде {(. *?)}, а затем просто подсчитывать совпадения. Если вам нужно обрабатывать такие случаи, как {0} {0} (который, как я полагаю, должен возвращать 1), то это делает его немного сложнее, но вы всегда можете поместить все совпадения в список, чтобы выделить Linq на нем, Я думаю что-то вроде кода ниже:

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:

Я хотел бы затронуть некоторые проблемы, затронутые в комментариях. Обновленный код, размещенный ниже, обрабатывает случаи, когда есть escape-последовательности (т.е. {{5}}), где не указаны параметры, а также возвращается значение самого высокого параметра + 1. Код предполагает, что входные строки будут быть хорошо сформированным, но этот компромисс может быть приемлемым в некоторых случаях. Например, если вы знаете, что входные строки определены в приложении и не генерируются пользователем, тогда обработка всех случаев краев может быть не нужна. Также возможно проверить все сообщения об ошибках, которые будут сгенерированы с помощью unit test. То, что мне нравится в этом решении, заключается в том, что он, скорее всего, справится с подавляющим большинством строк, которые ему бросают, и это более простое решение, чем тот, который был идентифицирован here (что предполагает повторную реализацию string.AppendFormat). Я бы объяснил, что этот код может не обрабатывать все случаи краев с помощью try-catch и просто возвращать "Invalid message message template" или что-то в этом роде.

Одним из возможных улучшений для кода ниже было бы обновление регулярного выражения, чтобы не возвращать ведущие символы "{". Это устранит необходимость в Replace ( "{", string.Empty). Опять же, этот код может быть не идеальным во всех случаях, но я чувствую, что он адекватно решает вопрос, как было задано.

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);

Ответ 2

Я думаю, что это будет обрабатывать скобки Escape и 0: 0000 stuff... даст наибольшее значение в квадратных скобках... так что в моем примере дается 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.