Вы можете использовать регулярное выражение, что-то вроде {(. *?)}, А затем просто считать совпадения. Если вам нужно обрабатывать такие случаи, как {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:
Я хотел бы ответить на некоторые вопросы, поднятые в комментариях. Обновленный код, размещенный ниже, обрабатывает случаи, когда существуют экранированные скобочные последовательности (т. Е. {{5}}), когда параметры не указаны, а также возвращает значение самого высокого параметра + 1. Код предполагает, что входные строки будут быть хорошо сформированным, но этот компромисс может быть приемлемым в некоторых случаях. Например, если вы знаете, что входные строки определены в приложении и не сгенерированы пользовательским вводом, тогда обработка всех крайних случаев может не потребоваться. Также может быть возможно проверить все сообщения об ошибках, которые будут сгенерированы, используя модульный тест. Что мне нравится в этом решении, так это то, что оно, скорее всего, будет обрабатывать подавляющее большинство брошенных в него строк, и это более простое решение, чем указанное здесь (что предполагает повторную реализацию строки .AppendFormat). Я бы учел тот факт, что этот код может не обрабатывать все крайние случаи, используя try-catch и просто возвращая «Неверный шаблон сообщения об ошибке» или что-то в этом роде.
Одним из возможных улучшений для приведенного ниже кода было бы обновление регулярного выражения, чтобы оно не возвращало начальные символы "{". Это исключило бы необходимость замены ("{", 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);