C# Именованные параметры в строку, которая заменяет значения параметров

Я хочу с хорошей производительностью (надеюсь) заменить именованный параметр в моей строке на именованный параметр из кода, например, мою строку:

"Hi {name}, do you like milk?"

Как я могу заменить {name} кодом, регулярными выражениями? Дорого? Какой способ вы рекомендуете?

Как они в примере NHibernates HQL заменяют :my_param на значение, определенное пользователем? Или в маршрутизации ASP.NET (MVC), которая мне больше нравится, "{controller}/{action}", new {controller = "Hello", ... }?


person Community    schedule 18.12.2008    source источник


Ответы (9)


Вы подтвердили, что регулярные выражения слишком дороги?

Стоимость регулярных выражений сильно преувеличена. Для такого простого шаблона производительность будет достаточно хорошей, возможно, лишь немногим хуже, чем у прямого поиска и замены. Кроме того, вы экспериментировали с флагом Compiled при построении регулярного выражения?

Тем не менее, разве вы не можете просто использовать самый простой способ, то есть Replace?

string varname = "name";
string pattern = "{" + varname + "}";
Console.WriteLine("Hi {name}".Replace(pattern, "Mike"));
person Konrad Rudolph    schedule 18.12.2008
comment
(Хотя вы имеете в виду Заменить (шаблон, Майк)) - person Jon Skeet; 19.12.2008
comment
@Джон: Спасибо. Зачем использовать компилятор, если у вас есть Джон? ;-) - person Konrad Rudolph; 19.12.2008

Regex, безусловно, является жизнеспособным вариантом, особенно с MatchEvaluator:

    Regex re = new Regex(@"\{(\w*?)\}", RegexOptions.Compiled); // store this...

    string input = "Hi {name}, do you like {food}?";

    Dictionary<string, string> vals = new Dictionary<string, string>();
    vals.Add("name", "Fred");
    vals.Add("food", "milk");

    string q = re.Replace(input, delegate(Match match)
    {
        string key = match.Groups[1].Value;
        return vals[key];
    });
person Marc Gravell    schedule 18.12.2008
comment
Черт... Как ты умудрился сделать это на 18 минут раньше меня? - person James Curran; 19.12.2008
comment
Если вы используете .NET 3.5, вы можете убить ключевое слово delegate. делегат (совпадение совпадения) может быть совпадением => - person steve_c; 19.12.2008
comment
@scalvert — точнее, C# 3.0, а не .NET 3.5; он будет работать и для .NET 2.0 с C# 3.0. - person Marc Gravell; 19.12.2008

Теперь, если у вас есть замены в словаре, например:

    var  replacements = new Dictionary<string, string>();
    replacements["name"] = "Mike";
    replacements["age"]= "20";

тогда регулярное выражение становится довольно простым:

Regex regex = new Regex(@"\{(?<key>\w+)\}");
    string formattext = "{name} is {age} years old";
    string newStr = regex.Replace(formattext, 
            match=>replacements[match.Groups[1].Captures[0].Value]);
person James Curran    schedule 18.12.2008
comment
+1 за краткость - из всех предложенных решений это мое любимое :-) - person mindplay.dk; 31.03.2011
comment
ненавижу регулярные выражения, но это довольно приятно. - person Brady Moritz; 02.12.2012

Подумав об этом, я понял, чего на самом деле хотел, так это того, чтобы String.Format() принимал IDictionary в качестве аргумента и чтобы шаблоны можно было писать с использованием имен вместо индексов.

Для подстановок строк с большим количеством возможных ключей/значений номера индексов приводят к неразборчивым шаблонам строк, а в некоторых случаях вы можете даже не знать, какие элементы будут иметь какой номер, поэтому я придумал следующее расширение:

https://gist.github.com/896724

По сути, это позволяет вам использовать строковые шаблоны с именами вместо чисел и словарь вместо массива, а также дает вам все другие хорошие функции String.Format(), позволяя использовать пользовательский IFormatProvider, если это необходимо, и позволяя использование всего обычного синтаксиса форматирования — точности, длины и т. д.

Отличным примером является пример, приведенный в справочном материале для String.Format. о том, как шаблоны со многими пронумерованными элементами становятся совершенно неразборчивыми — портируя этот пример для использования этого нового метода расширения, вы получаете что-то вроде этого:

var replacements = new Dictionary<String, object>()
                       {
                           { "date1", new DateTime(2009, 7, 1) },
                           { "hiTime", new TimeSpan(14, 17, 32) },
                           { "hiTemp", 62.1m },
                           { "loTime", new TimeSpan(3, 16, 10) },
                           { "loTemp", 54.8m }
                       };

var template =
    "Temperature on {date1:d}:\n{hiTime,11}: {hiTemp} degrees (hi)\n{loTime,11}: {loTemp} degrees (lo)";

var result = template.Subtitute(replacements);

Как кто-то указал, если то, что вы пишете, должно быть сильно оптимизировано, не используйте что-то подобное - если вам нужно форматировать таким образом миллионы строк в цикле, накладные расходы памяти и производительности могут быть значительными.

С другой стороны, если вы беспокоитесь о написании разборчивого, поддерживаемого кода, и если вы выполняете, скажем, кучу операций с базой данных, по большому счету, эта функция не добавит каких-либо значительных накладных расходов.

...

Для удобства я попытался добавить метод, который принимал бы анонимный объект вместо словаря:

public static String Substitute(this String template, object obj)
{
    return Substitute(
        template,
        obj.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(obj, null))
    );
}

По какой-то причине это не работает — передача анонимного объекта, такого как new { name: "value" }, этому методу расширения дает сообщение об ошибке времени компиляции, в котором говорится, что лучшим совпадением является версия IDictionary этого метода. Не знаю, как это исправить. (кто угодно?)

person mindplay.dk    schedule 31.03.2011
comment
Вы когда-нибудь выясняли анонимный тип для этого? это было бы действительно круто. - person Brady Moritz; 02.12.2012
comment
Я не помню - это было давно, и я сейчас не работаю с ASP.NET... Я посмотрел тогдашний код, и он пошел в производство, выглядя так же - я смутно припоминаю проблему будучи как-то не с этим кодом, а с Consumer-кодом. Это лучшая подсказка, которую я могу вам дать. - person mindplay.dk; 17.12.2012

Как насчет

stringVar = "Hello, {0}. How are you doing?";
arg1 = "John";    // or args[0]
String.Format(stringVar, arg1)

Вы даже можете иметь несколько аргументов, просто увеличьте {x} и добавьте еще один параметр в метод Format(). Не уверен, что это другое, но и "string", и "String" имеют этот метод.

person sosdude    schedule 03.02.2010
comment
ему нужны именованные параметры, а не упорядоченные. - person Brady Moritz; 02.12.2012

Скомпилированное регулярное выражение может помочь, особенно если нужно заменить много токенов. Если их всего несколько, а производительность является ключевым фактором, я бы просто нашел токен по индексу и заменил его с помощью строковых функций. Хотите верьте, хотите нет, это будет быстрее, чем регулярное выражение.

person Andrew Hare    schedule 18.12.2008

Попробуйте использовать StringTemplate. Он намного мощнее, но выполняет свою работу безупречно.

person Community    schedule 29.03.2009

или попробуйте это с Linq, если у вас есть все ваши значения замены, хранящиеся в словаре obj.

Например:

Dictionary<string,string> dict = new Dictionary<string,string>();
dict.add("replace1","newVal1");
dict.add("replace2","newVal2");
dict.add("replace3","newVal3");

var newstr = dict.Aggregate(str, (current, value) => current.Replace(value.Key, value.Value));

dict – это объект словаря, определяемый парами поиска и замены. str — это ваша строка, которую вам нужно заменить.

person Allen Wang    schedule 31.10.2011

Я бы выбрал решение mindplay.dk... Работает очень хорошо.

И, с небольшой модификацией, он поддерживает шаблоны-из-шаблонов, например «Привет, {имя}, тебе нравится {0}?», заменяя {имя}, но сохраняя {0}:

В данном источнике (https://gist.github.com/896724) замените следующим образом:

        var format = Pattern.Replace(
            template,
            match =>
                {
                    var name = match.Groups[1].Captures[0].Value;

                    if (!int.TryParse(name, out parsedInt))
                    {
                        if (!map.ContainsKey(name))
                        {
                            map[name] = map.Count;
                            list.Add(dictionary.ContainsKey(name) ? dictionary[name] : null);
                        }

                        return "{" + map[name] + match.Groups[2].Captures[0].Value + "}";
                    }
                    else return "{{" + name + "}}";
                }
            );

Кроме того, он поддерживает длину ({имя, 30}), а также спецификатор формата или их комбинацию.

person Edwin    schedule 09.11.2011