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

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

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

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

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

9 ответов


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

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

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

string varname = "name";
string pattern = "{" + varname + "}";
Console.WriteLine("Hi {name}".Replace(pattern, "Mike"));

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

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

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

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

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

https://gist.github.com/896724

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

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

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 этого метода. Не знаю, как это исправить. (кто?)


как о

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

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


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


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


или попробуйте это с Linq, если у вас есть все ваши значения replace, хранящиеся в 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));

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


Я бы пошел на игру ума.dk решение... Работать достаточно хорошо.

и, с небольшой модификацией, он поддерживает шаблоны-это шаблоны, как "Привет {имя}, вам нравится {0}?", заменив {name}, но сохранив {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 + "}}";
                }
            );

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