Ответ 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);