Форматирование именованных строк в C #

Есть ли способ отформатировать строку по имени, а не по позиции в C #?

В python я могу сделать что-то вроде этого примера (бесстыдно украденного с здесь):

>>> print '%(language)s has %(#)03d quote types.' % \
      {'language': "Python", "#": 2}
Python has 002 quote types.

Есть ли способ сделать это на C #? Скажите, например:

String.Format("{some_variable}: {some_other_variable}", ...);

Было бы неплохо сделать это, используя имя переменной, но словарь тоже приемлем.


person Jason Baker    schedule 01.10.2008    source источник
comment
Мне это тоже не хватает от Руби.   -  person JesperE    schedule 01.10.2008
comment
Я думаю, что ваш пример слишком упрощен и заставляет людей давать вам бесполезные ответы. Возможно, использование переменной в строке более одного раза будет более наглядным.   -  person Wedge    schedule 01.10.2008
comment
На самом деле, КОНКРЕТНАЯ путаница заключается в использовании String.Format. Это поддается таким ответам, как мой, которые бесполезны, потому что они не ориентированы на переменную, но точны в том, что касается String.Format.   -  person John Rudy    schedule 01.10.2008
comment
Вызов String.Format, очевидно, является надуманным примером. Если, конечно, вы не знали, что вызов String.Format с многоточием невозможен. Проблема заключалась в том, что я не сказал, что хочу, чтобы форматирование происходило по именованным параметрам, а не по положению, которое было исправлено.   -  person Jason Baker    schedule 01.10.2008
comment
К вашему сведению: отправлено в пользовательский голос MS Connect с просьбой сделать его стандартной функцией платформы. Для всех, кто заинтересован, проголосуйте за: visualstudio .uservoice.com / forum / 121579-visual-studio /   -  person JohnLBevan    schedule 25.04.2014


Ответы (18)


Для этого нет встроенного метода.

Вот один метод

string myString = "{foo} is {bar} and {yadi} is {yada}".Inject(o);

Вот еще один < / а>

Status.Text = "{UserName} last logged in at {LastLoginDate}".FormatWith(user);

Третий улучшенный метод частично основан на двух вышеупомянутых, от Фила Хаака

person John Sheehan    schedule 01.10.2008
comment
Мне очень понравилось использовать FormatWith (), но я хотел указать на проблему, с которой недавно столкнулся. Реализация полагается на DataBinder из System.Web.UI, который не поддерживается в SQL CLR. Inject (o) не полагается на связыватель данных, что сделало его полезным для замены нескольких токенов в моем объекте SQL CLR. - person EBarr; 31.10.2010
comment
Возможно, вы сможете обновить первое предложение своего ответа. Интерполяция строк присутствует в C # и VB в течение нескольких месяцев (наконец ...). Ваш ответ находится вверху, поэтому читателям может быть полезно связать их с некоторыми обновленными ресурсами .NET. - person miroxlav; 24.02.2015
comment
@miroxlav это не совсем то же самое. Вы не можете передавать интерполированные строки: stackoverflow.com/q/31987232/213725 - person DixonD; 09.09.2015
comment
@DixonD - вы определенно правы, но это не было их целью. В вопросах и ответах, которые вы связали, OP пытается ссылаться на имя переменной еще до того, как оно существует. Не очень хорошая идея, но если кто-то настаивает на этом, он может построить специализированный парсер. Но я бы не стал связываться с общей концепцией интерполяции строк. - person miroxlav; 09.09.2015

У меня есть реализация, которую я только что разместил в своем блоге: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx

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

person Haacked    schedule 05.01.2009
comment
Код доступен для скачивания в статье 404-х годов. Я тоже очень хочу это увидеть. - person quentin-starin; 28.09.2010
comment
@qes: в комментариях размещена обновленная ссылка: code.haacked.com/util/NamedStringFormatSolution. zip - person Der Hochstapler; 28.08.2012
comment
@OliverSalzburg: Я уже давно использую SmartFormat для всех моих нужд форматирования, мне это нравится. github.com/scottrippey/SmartFormat - person quentin-starin; 30.08.2012
comment
@qes: Не могли бы вы написать и ответить об этом и показать, как это работает? Выглядит интересно - person Der Hochstapler; 30.08.2012
comment
@qes: Вы обязательно должны добавить SmartFormat в качестве ответа, так как он очень хорош и активно поддерживается (2015). - person Răzvan Flavius Panda; 14.07.2015
comment
NamedFormat("{a} is not, {b}", new {a:"my a",b:"my b"}); Это так же коротко, как волшебство - person Fabrice T; 22.10.2020

Интерполированные строки были добавлены в C # 6.0 и Visual Basic 14

Оба они были представлены с помощью нового компилятора Roslyn в Visual Studio 2015.

  • C # 6.0:

    return "\{someVariable} and also \{someOtherVariable}" OR
    return $"{someVariable} and also {someOtherVariable}"

  • VB 14:

    return $"{someVariable} and also {someOtherVariable}"

Примечательные особенности (в Visual Studio 2015 IDE):

  • Поддерживается раскраска синтаксиса - выделяются переменные, содержащиеся в строках
  • Поддерживается рефакторинг - при переименовании переменные, содержащиеся в строках, тоже переименовываются.
  • на самом деле поддерживаются не только имена переменных, но и выражения - например, не только {index} работает, но и {(index + 1).ToString().Trim()}

Наслаждаться! (и нажмите "Отправить улыбку" в VS)

person miroxlav    schedule 01.12.2014
comment
Вопрос помечен тегом .net 3.5, поэтому ваша информация действительна, но не является альтернативой. - person Douglas Gandini; 20.01.2016
comment
@miroxlav - Насчет версии фреймворка ты прав. Строковая интерполяция зависит только от нового компилятора Roslyn, используемого в VS 2015. - person Douglas Gandini; 20.01.2016
comment
Это также не будет работать, если ваша строка формата не будет помещена в сам код. т.е. он не будет работать, если ваша строка формата поступает из внешнего источника, такого как файл конфигурации или база данных. - person Craig Brett; 07.06.2019

Вы также можете использовать такие анонимные типы:

    public string Format(string input, object p)
    {
        foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(p))
            input = input.Replace("{" + prop.Name + "}", (prop.GetValue(p) ?? "(null)").ToString());

        return input;
    }

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

Format("test {first} and {another}", new { first = "something", another = "something else" })
person Doggett    schedule 02.11.2010
comment
Идеально подходит для тех из нас, кто все еще использует 2.0. Да, я знаю .... Это простое и понятное решение. И ЭТО РАБОТАЕТ !!! - person Brad Bruce; 23.04.2012

Кажется, не существует способа сделать это из коробки. Хотя кажется возможным реализовать собственный IFormatProvider, который ссылается на IDictionary для значений.

var Stuff = new Dictionary<string, object> {
   { "language", "Python" },
   { "#", 2 }
};
var Formatter = new DictionaryFormatProvider();

// Interpret {0:x} where {0}=IDictionary and "x" is hash key
Console.WriteLine string.Format(Formatter, "{0:language} has {0:#} quote types", Stuff);

Выходы:

Python has 2 quote types

Предостережение: вы не можете смешивать FormatProviders, поэтому модное форматирование текста нельзя использовать одновременно.

person spoulson    schedule 01.10.2008
comment
+1 за обрисовку, ИМХО, лучший концептуальный метод, у которого есть хорошая реализация на mo.notono.us/2008/07/c-stringinject-format-strings-by-key.html - другие сообщения включают это, но также предлагают методы на основе отражения, которые , ИМХО, довольно злые - person Adam Ralph; 02.11.2010

Сама платформа не предоставляет возможности для этого, но вы можете прочитать этот пост Скотта Хансельмана. Пример использования:

Person p = new Person();  
string foo = p.ToString("{Money:C} {LastName}, {ScottName} {BirthDate}");  
Assert.AreEqual("$3.43 Hanselman, {ScottName} 1/22/1974 12:00:00 AM", foo); 

Этот код Джеймса Ньютона-Кинга аналогичен и работает с подсвойствами и индексами,

string foo = "Top result for {Name} was {Results[0].Name}".FormatWith(student));

Код Джеймса полагается на System.Web.UI.DataBinder для анализа строки и требует ссылки на System.Web, что некоторым людям не нравится делать в не веб-приложениях.

РЕДАКТИРОВАТЬ: Да, и они прекрасно работают с анонимными типами, если у вас нет объекта со свойствами, готовыми для него:

string name = ...;
DateTime date = ...;
string foo = "{Name} - {Birthday}".FormatWith(new { Name = name, Birthday = date });
person Lucas    schedule 01.10.2008

См. https://stackoverflow.com/questions/271398?page=2#358259

С помощью связанного расширения вы можете написать это:

var str = "{foo} {bar} {baz}".Format(foo=>"foo", bar=>2, baz=>new object());

и вы получите "foo 2 System.Object ".

person Mark Cidade    schedule 05.01.2009

Я думаю, что самое близкое, что вы получите, - это индексированный формат:

String.Format("{0} has {1} quote types.", "C#", "1");

Также есть String.Replace (), если вы готовы сделать это в несколько этапов и уверены, что вы не найдете свои «переменные» где-либо еще в строке:

string MyString = "{language} has {n} quote types.";
MyString = MyString.Replace("{language}", "C#").Replace("{n}", "1");

Расширяя это, чтобы использовать список:

List<KeyValuePair<string, string>> replacements = GetFormatDictionary();  
foreach (KeyValuePair<string, string> item in replacements)
{
    MyString = MyString.Replace(item.Key, item.Value);
}

Вы можете сделать это и с помощью Dictionary ‹string, string>, повторяя его коллекции .Keys, но с помощью List‹ KeyValuePair ‹string, string >> мы можем воспользоваться преимуществом метода List .ForEach () и снова сжать его до однострочный:

replacements.ForEach(delegate(KeyValuePair<string,string>) item) { MyString = MyString.Replace(item.Key, item.Value);});

Лямбда была бы еще проще, но я все еще использую .Net 2.0. Также обратите внимание, что производительность .Replace () не является звездной при итеративном использовании, поскольку строки в .Net неизменяемы. Кроме того, для этого требуется, чтобы переменная MyString была определена таким образом, чтобы она была доступна делегату, поэтому она еще не идеальна.

person Community    schedule 01.10.2008
comment
Что ж, это не самое красивое решение, но на данный момент я собираюсь использовать его. Единственное, что я сделал иначе, - это использовал StringBuilder вместо строки, чтобы не создавать новые строки. - person Jason Baker; 02.10.2008

Моя библиотека с открытым исходным кодом Regextra поддерживает именованное форматирование (среди прочего). В настоящее время он нацелен на .NET 4.0+ и доступен на NuGet. У меня также есть вводная запись в блоге об этом: Regextra: помогает вам уменьшить ваши (проблемы) {2} .

Именованный бит форматирования поддерживает:

  • Базовое форматирование
  • Форматирование вложенных свойств
  • Форматирование словаря
  • Экранирование разделителей
  • Форматирование строк Standard / Custom / IFormatProvider

Пример:

var order = new
{
    Description = "Widget",
    OrderDate = DateTime.Now,
    Details = new
    {
        UnitPrice = 1500
    }
};

string template = "We just shipped your order of '{Description}', placed on {OrderDate:d}. Your {{credit}} card will be billed {Details.UnitPrice:C}.";

string result = Template.Format(template, order);
// or use the extension: template.FormatTemplate(order);

Результат:

Мы только что отправили ваш заказ на "Виджет", размещенный 28.02.2014. С вашей {кредитной} карты будет выставлен счет в размере 1500 долларов США.

Посмотрите ссылку на проект на GitHub (см. Выше) и в вики для других примеров.

person Ahmad Mageed    schedule 19.04.2014
comment
Ух ты, это выглядит потрясающе, особенно при работе с некоторыми из наиболее сложных примеров форматов, с которыми можно встретиться. - person Nicholas Petersen; 18.07.2014

Отметьте это:

public static string StringFormat(string format, object source)
{
    var matches = Regex.Matches(format, @"\{(.+?)\}");
    List<string> keys = (from Match matche in matches select matche.Groups[1].Value).ToList();

    return keys.Aggregate(
        format,
        (current, key) =>
        {
            int colonIndex = key.IndexOf(':');
            return current.Replace(
                "{" + key + "}",
                colonIndex > 0
                    ? DataBinder.Eval(source, key.Substring(0, colonIndex), "{0:" + key.Substring(colonIndex + 1) + "}")
                    : DataBinder.Eval(source, key).ToString());
        });
}

Образец:

string format = "{foo} is a {bar} is a {baz} is a {qux:#.#} is a really big {fizzle}";
var o = new { foo = 123, bar = true, baz = "this is a test", qux = 123.45, fizzle = DateTime.Now };
Console.WriteLine(StringFormat(format, o));

Производительность вполне нормальная по сравнению с другими решениями.

person Pavlo Neiman    schedule 22.09.2011

Я сомневаюсь, что это будет возможно. Первое, что приходит в голову, - как вы собираетесь получить доступ к именам локальных переменных?

Однако для этого может быть какой-нибудь умный способ использовать LINQ и лямбда-выражения.

person leppie    schedule 01.10.2008
comment
@leppie: +1, если вы дадите мне LINQ + Lambda для этого; D (хорошо, +1 за соответствующий ответ) - person user7116; 01.10.2008
comment
Я тоже хотел бы это увидеть! Может быть, я приму этот вызов! - person leppie; 01.10.2008
comment
Я подумал, что это невозможно сделать с именами переменных, но вставил это туда на случай, если я ошибаюсь. :) Со словарем тоже никак не получается? - person Jason Baker; 01.10.2008
comment
Я пробовал и кое-что получил, но посчитал это слишком некрасивым и трудным в использовании. Это выглядело бы так: строка s = format (f = ›f ({привет} {мир}, привет, мир)); - person leppie; 02.10.2008

Вот один, который я сделал некоторое время назад. Он расширяет String с помощью метода Format, принимающего единственный аргумент. Приятно то, что он будет использовать стандартную строку string.Format, если вы предоставите простой аргумент, например int, но если вы используете что-то вроде анонимного типа, он тоже будет работать.

Пример использования:

"The {Name} family has {Children} children".Format(new { Children = 4, Name = "Smith" })

Результатом будет «В семье Смитов 4 ребенка».

Он не делает безумных привязок, таких как массивы и индексаторы. Но он супер простой и высокопроизводительный.

    public static class AdvancedFormatString
{

    /// <summary>
    /// An advanced version of string.Format.  If you pass a primitive object (string, int, etc), it acts like the regular string.Format.  If you pass an anonmymous type, you can name the paramters by property name.
    /// </summary>
    /// <param name="formatString"></param>
    /// <param name="arg"></param>
    /// <returns></returns>
    /// <example>
    /// "The {Name} family has {Children} children".Format(new { Children = 4, Name = "Smith" })
    /// 
    /// results in 
    /// "This Smith family has 4 children
    /// </example>
    public static string Format(this string formatString, object arg, IFormatProvider format = null)
    {
        if (arg == null)
            return formatString;

        var type = arg.GetType();
        if (Type.GetTypeCode(type) != TypeCode.Object || type.IsPrimitive)
            return string.Format(format, formatString, arg);

        var properties = TypeDescriptor.GetProperties(arg);
        return formatString.Format((property) =>
            {
                var value = properties[property].GetValue(arg);
                return Convert.ToString(value, format);
            });
    }


    public static string Format(this string formatString, Func<string, string> formatFragmentHandler)
    {
        if (string.IsNullOrEmpty(formatString))
            return formatString;
        Fragment[] fragments = GetParsedFragments(formatString);
        if (fragments == null || fragments.Length == 0)
            return formatString;

        return string.Join(string.Empty, fragments.Select(fragment =>
            {
                if (fragment.Type == FragmentType.Literal)
                    return fragment.Value;
                else
                    return formatFragmentHandler(fragment.Value);
            }).ToArray());
    }


    private static Fragment[] GetParsedFragments(string formatString)
    {
        Fragment[] fragments;
        if ( parsedStrings.TryGetValue(formatString, out fragments) )
        {
            return fragments;
        }
        lock (parsedStringsLock)
        {
            if ( !parsedStrings.TryGetValue(formatString, out fragments) )
            {
                fragments = Parse(formatString);
                parsedStrings.Add(formatString, fragments);
            }
        }
        return fragments;
    }

    private static Object parsedStringsLock = new Object();
    private static Dictionary<string,Fragment[]> parsedStrings = new Dictionary<string,Fragment[]>(StringComparer.Ordinal);

    const char OpeningDelimiter = '{';
    const char ClosingDelimiter = '}';

    /// <summary>
    /// Parses the given format string into a list of fragments.
    /// </summary>
    /// <param name="format"></param>
    /// <returns></returns>
    static Fragment[] Parse(string format)
    {
        int lastCharIndex = format.Length - 1;
        int currFragEndIndex;
        Fragment currFrag = ParseFragment(format, 0, out currFragEndIndex);

        if (currFragEndIndex == lastCharIndex)
        {
            return new Fragment[] { currFrag };
        }

        List<Fragment> fragments = new List<Fragment>();
        while (true)
        {
            fragments.Add(currFrag);
            if (currFragEndIndex == lastCharIndex)
            {
                break;
            }
            currFrag = ParseFragment(format, currFragEndIndex + 1, out currFragEndIndex);
        }
        return fragments.ToArray();

    }

    /// <summary>
    /// Finds the next delimiter from the starting index.
    /// </summary>
    static Fragment ParseFragment(string format, int startIndex, out int fragmentEndIndex)
    {
        bool foundEscapedDelimiter = false;
        FragmentType type = FragmentType.Literal;

        int numChars = format.Length;
        for (int i = startIndex; i < numChars; i++)
        {
            char currChar = format[i];
            bool isOpenBrace = currChar == OpeningDelimiter;
            bool isCloseBrace = isOpenBrace ? false : currChar == ClosingDelimiter;

            if (!isOpenBrace && !isCloseBrace)
            {
                continue;
            }
            else if (i < (numChars - 1) && format[i + 1] == currChar)
            {//{{ or }}
                i++;
                foundEscapedDelimiter = true;
            }
            else if (isOpenBrace)
            {
                if (i == startIndex)
                {
                    type = FragmentType.FormatItem;
                }
                else
                {

                    if (type == FragmentType.FormatItem)
                        throw new FormatException("Two consequtive unescaped { format item openers were found.  Either close the first or escape any literals with another {.");

                    //curr character is the opening of a new format item.  so we close this literal out
                    string literal = format.Substring(startIndex, i - startIndex);
                    if (foundEscapedDelimiter)
                        literal = ReplaceEscapes(literal);

                    fragmentEndIndex = i - 1;
                    return new Fragment(FragmentType.Literal, literal);
                }
            }
            else
            {//close bracket
                if (i == startIndex || type == FragmentType.Literal)
                    throw new FormatException("A } closing brace existed without an opening { brace.");

                string formatItem = format.Substring(startIndex + 1, i - startIndex - 1);
                if (foundEscapedDelimiter)
                    formatItem = ReplaceEscapes(formatItem);//a format item with a { or } in its name is crazy but it could be done
                fragmentEndIndex = i;
                return new Fragment(FragmentType.FormatItem, formatItem);
            }
        }

        if (type == FragmentType.FormatItem)
            throw new FormatException("A format item was opened with { but was never closed.");

        fragmentEndIndex = numChars - 1;
        string literalValue = format.Substring(startIndex);
        if (foundEscapedDelimiter)
            literalValue = ReplaceEscapes(literalValue);

        return new Fragment(FragmentType.Literal, literalValue);

    }

    /// <summary>
    /// Replaces escaped brackets, turning '{{' and '}}' into '{' and '}', respectively.
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    static string ReplaceEscapes(string value)
    {
        return value.Replace("{{", "{").Replace("}}", "}");
    }

    private enum FragmentType
    {
        Literal,
        FormatItem
    }

    private class Fragment
    {

        public Fragment(FragmentType type, string value)
        {
            Type = type;
            Value = value;
        }

        public FragmentType Type
        {
            get;
            private set;
        }

        /// <summary>
        /// The literal value, or the name of the fragment, depending on fragment type.
        /// </summary>
        public string Value
        {
            get;
            private set;
        }


    }

}
person Steve Potter    schedule 29.01.2011

private static Regex s_NamedFormatRegex = new Regex(@"\{(?!\{)(?<key>[\w]+)(:(?<fmt>(\{\{|\}\}|[^\{\}])*)?)?\}", RegexOptions.Compiled);

public static StringBuilder AppendNamedFormat(this StringBuilder builder,IFormatProvider provider, string format, IDictionary<string, object> args)
{
    if (builder == null) throw new ArgumentNullException("builder");
    var str = s_NamedFormatRegex.Replace(format, (mt) => {
        string key = mt.Groups["key"].Value;
        string fmt = mt.Groups["fmt"].Value;
        object value = null;
        if (args.TryGetValue(key,out value)) {
            return string.Format(provider, "{0:" + fmt + "}", value);
        } else {
            return mt.Value;
        }
    });
    builder.Append(str);
    return builder;
}

public static StringBuilder AppendNamedFormat(this StringBuilder builder, string format, IDictionary<string, object> args)
{
    if (builder == null) throw new ArgumentNullException("builder");
    return builder.AppendNamedFormat(null, format, args);
}

Пример:

var builder = new StringBuilder();
builder.AppendNamedFormat(
@"你好,{Name},今天是{Date:yyyy/MM/dd}, 这是你第{LoginTimes}次登录,积分{Score:{{ 0.00 }}}",
new Dictionary<string, object>() { 
    { "Name", "wayjet" },
    { "LoginTimes",18 },
    { "Score", 100.4 },
    { "Date",DateTime.Now }
});

Выход: 你好, wayjet, 天 是 04.05.2011, 是 你 第 18 次 登录 , 积分 {100.40}

person wayjet    schedule 04.05.2011

вот простой метод для любого объекта:

    using System.Text.RegularExpressions;
    using System.ComponentModel;

    public static string StringWithFormat(string format, object args)
    {
        Regex r = new Regex(@"\{([A-Za-z0-9_]+)\}");

        MatchCollection m = r.Matches(format);

        var properties = TypeDescriptor.GetProperties(args);

        foreach (Match item in m)
        {
            try
            {
                string propertyName = item.Groups[1].Value;
                format = format.Replace(item.Value, properties[propertyName].GetValue(args).ToString());
            }
            catch
            {
                throw new FormatException("The format string is not valid");
            }
        }

        return format;
    }

А вот как им пользоваться:

 DateTime date = DateTime.Now;
 string dateString = StringWithFormat("{Month}/{Day}/{Year}", date);

вывод: 27.02.2012

person Ashkan Ghodrat    schedule 26.02.2012

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

https://github.com/SergueiFedorov/NamedFormatString

C # 6.0 добавляет эту функциональность прямо в спецификацию языка, поэтому NamedFormatString предназначен для обратной совместимости.

person Serguei Fedorov    schedule 14.05.2015

Я решил это немного иначе, чем существующие решения. Он выполняет суть замены названного элемента (а не бит отражения, как некоторые сделали). Это очень быстро и просто ... Это мое решение:

/// <summary>
/// Formats a string with named format items given a template dictionary of the items values to use.
/// </summary>
public class StringTemplateFormatter
{
    private readonly IFormatProvider _formatProvider;

    /// <summary>
    /// Constructs the formatter with the specified <see cref="IFormatProvider"/>.
    /// This is defaulted to <see cref="CultureInfo.CurrentCulture">CultureInfo.CurrentCulture</see> if none is provided.
    /// </summary>
    /// <param name="formatProvider"></param>
    public StringTemplateFormatter(IFormatProvider formatProvider = null)
    {
        _formatProvider = formatProvider ?? CultureInfo.CurrentCulture;
    }

    /// <summary>
    /// Formats a string with named format items given a template dictionary of the items values to use.
    /// </summary>
    /// <param name="text">The text template</param>
    /// <param name="templateValues">The named values to use as replacements in the formatted string.</param>
    /// <returns>The resultant text string with the template values replaced.</returns>
    public string FormatTemplate(string text, Dictionary<string, object> templateValues)
    {
        var formattableString = text;
        var values = new List<object>();
        foreach (KeyValuePair<string, object> value in templateValues)
        {
            var index = values.Count;
            formattableString = ReplaceFormattableItem(formattableString, value.Key, index);
            values.Add(value.Value);
        }
        return String.Format(_formatProvider, formattableString, values.ToArray());
    }

    /// <summary>
    /// Convert named string template item to numbered string template item that can be accepted by <see cref="string.Format(string,object[])">String.Format</see>
    /// </summary>
    /// <param name="formattableString">The string containing the named format item</param>
    /// <param name="itemName">The name of the format item</param>
    /// <param name="index">The index to use for the item value</param>
    /// <returns>The formattable string with the named item substituted with the numbered format item.</returns>
    private static string ReplaceFormattableItem(string formattableString, string itemName, int index)
    {
        return formattableString
            .Replace("{" + itemName + "}", "{" + index + "}")
            .Replace("{" + itemName + ",", "{" + index + ",")
            .Replace("{" + itemName + ":", "{" + index + ":");
    }
}

Он используется следующим образом:

    [Test]
    public void FormatTemplate_GivenANamedGuid_FormattedWithB_ShouldFormatCorrectly()
    {
        // Arrange
        var template = "My guid {MyGuid:B} is awesome!";
        var templateValues = new Dictionary<string, object> { { "MyGuid", new Guid("{A4D2A7F1-421C-4A1D-9CB2-9C2E70B05E19}") } };
        var sut = new StringTemplateFormatter();
        // Act
        var result = sut.FormatTemplate(template, templateValues);
        //Assert
        Assert.That(result, Is.EqualTo("My guid {a4d2a7f1-421c-4a1d-9cb2-9c2e70b05e19} is awesome!"));
    }

Надеюсь, кто-то сочтет это полезным!

person Mark Whitfeld    schedule 19.11.2015

Несмотря на то, что принятый ответ дает несколько хороших примеров, .Inject, а также некоторые примеры Haack не обрабатывают экранирование. Многие также сильно полагаются на Regex (медленнее) или DataBinder.Eval, который недоступен в .NET Core и в некоторых других средах.

Имея это в виду, я написал простой синтаксический анализатор на основе конечного автомата, который обрабатывает символы и записывает их на выход StringBuilder, символ за символом. Он реализован как String методы расширения и может принимать как Dictionary<string, object>, так и object с параметрами в качестве входных данных (с использованием отражения).

Он обрабатывает неограниченное количество уровней {{{escaping}}} и выдает FormatException, когда ввод содержит несбалансированные фигурные скобки и / или другие ошибки.

public static class StringExtension {
    /// <summary>
    /// Extension method that replaces keys in a string with the values of matching object properties.
    /// </summary>
    /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
    /// <param name="injectionObject">The object whose properties should be injected in the string</param>
    /// <returns>A version of the formatString string with keys replaced by (formatted) key values.</returns>
    public static string FormatWith(this string formatString, object injectionObject) {
        return formatString.FormatWith(GetPropertiesDictionary(injectionObject));
    }

    /// <summary>
    /// Extension method that replaces keys in a string with the values of matching dictionary entries.
    /// </summary>
    /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
    /// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
    /// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
    public static string FormatWith(this string formatString, IDictionary<string, object> dictionary) {
        char openBraceChar = '{';
        char closeBraceChar = '}';

        return FormatWith(formatString, dictionary, openBraceChar, closeBraceChar);
    }
        /// <summary>
        /// Extension method that replaces keys in a string with the values of matching dictionary entries.
        /// </summary>
        /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
        /// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
        /// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
    public static string FormatWith(this string formatString, IDictionary<string, object> dictionary, char openBraceChar, char closeBraceChar) {
        string result = formatString;
        if (dictionary == null || formatString == null)
            return result;

        // start the state machine!

        // ballpark output string as two times the length of the input string for performance (avoids reallocating the buffer as often).
        StringBuilder outputString = new StringBuilder(formatString.Length * 2);
        StringBuilder currentKey = new StringBuilder();

        bool insideBraces = false;

        int index = 0;
        while (index < formatString.Length) {
            if (!insideBraces) {
                // currently not inside a pair of braces in the format string
                if (formatString[index] == openBraceChar) {
                    // check if the brace is escaped
                    if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
                        // add a brace to the output string
                        outputString.Append(openBraceChar);
                        // skip over braces
                        index += 2;
                        continue;
                    }
                    else {
                        // not an escaped brace, set state to inside brace
                        insideBraces = true;
                        index++;
                        continue;
                    }
                }
                else if (formatString[index] == closeBraceChar) {
                    // handle case where closing brace is encountered outside braces
                    if (index < formatString.Length - 1 && formatString[index + 1] == closeBraceChar) {
                        // this is an escaped closing brace, this is okay
                        // add a closing brace to the output string
                        outputString.Append(closeBraceChar);
                        // skip over braces
                        index += 2;
                        continue;
                    }
                    else {
                        // this is an unescaped closing brace outside of braces.
                        // throw a format exception
                        throw new FormatException($"Unmatched closing brace at position {index}");
                    }
                }
                else {
                    // the character has no special meaning, add it to the output string
                    outputString.Append(formatString[index]);
                    // move onto next character
                    index++;
                    continue;
                }
            }
            else {
                // currently inside a pair of braces in the format string
                // found an opening brace
                if (formatString[index] == openBraceChar) {
                    // check if the brace is escaped
                    if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
                        // there are escaped braces within the key
                        // this is illegal, throw a format exception
                        throw new FormatException($"Illegal escaped opening braces within a parameter - index: {index}");
                    }
                    else {
                        // not an escaped brace, we have an unexpected opening brace within a pair of braces
                        throw new FormatException($"Unexpected opening brace inside a parameter - index: {index}");
                    }
                }
                else if (formatString[index] == closeBraceChar) {
                    // handle case where closing brace is encountered inside braces
                    // don't attempt to check for escaped braces here - always assume the first brace closes the braces
                    // since we cannot have escaped braces within parameters.

                    // set the state to be outside of any braces
                    insideBraces = false;

                    // jump over brace
                    index++;

                    // at this stage, a key is stored in current key that represents the text between the two braces
                    // do a lookup on this key
                    string key = currentKey.ToString();
                    // clear the stringbuilder for the key
                    currentKey.Clear();

                    object outObject;

                    if (!dictionary.TryGetValue(key, out outObject)) {
                        // the key was not found as a possible replacement, throw exception
                        throw new FormatException($"The parameter \"{key}\" was not present in the lookup dictionary");
                    }

                    // we now have the replacement value, add the value to the output string
                    outputString.Append(outObject);

                    // jump to next state
                    continue;
                } // if }
                else {
                    // character has no special meaning, add it to the current key
                    currentKey.Append(formatString[index]);
                    // move onto next character
                    index++;
                    continue;
                } // else
            } // if inside brace
        } // while

        // after the loop, if all braces were balanced, we should be outside all braces
        // if we're not, the input string was misformatted.
        if (insideBraces) {
            throw new FormatException("The format string ended before the parameter was closed.");
        }

        return outputString.ToString();
    }

    /// <summary>
    /// Creates a Dictionary from an objects properties, with the Key being the property's
    /// name and the Value being the properties value (of type object)
    /// </summary>
    /// <param name="properties">An object who's properties will be used</param>
    /// <returns>A <see cref="Dictionary"/> of property values </returns>
    private static Dictionary<string, object> GetPropertiesDictionary(object properties) {
        Dictionary<string, object> values = null;
        if (properties != null) {
            values = new Dictionary<string, object>();
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(properties);
            foreach (PropertyDescriptor prop in props) {
                values.Add(prop.Name, prop.GetValue(properties));
            }
        }
        return values;
    }
}

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

person Ryan    schedule 23.02.2016

string language = "Python";
int numquotes = 2;
string output = language + " has "+ numquotes + " language types.";

Изменить: то, что я должен был сказать, было: «Нет, я не верю, что то, что вы хотите сделать, поддерживается C #. Это настолько близко, насколько вы собираетесь получить».

person Kevin    schedule 01.10.2008
comment
Мне любопытно, что проголосовали против. Кто-нибудь хочет сказать мне, почему? - person Kevin; 01.10.2008
comment
Таким образом, string.format будет выполнять эту операцию на 4 / десять тысячных секунды быстрее. Если эта функция будет вызываться на тонну, вы можете заметить эту разницу. Но он, по крайней мере, отвечает на его вопрос, а не просто говорит ему делать это так, как он уже сказал, что не хочет этого делать. - person Kevin; 01.10.2008
comment
Я не голосовал за вас, но я бы не стал реализовывать это в основном потому, что я считаю уродливым выполнение множества конкатенаций строк. Но это мое личное мнение. - person Jason Baker; 02.10.2008
comment
Странно, что за это так проголосовали. Попробуйте расширить свой ответ, чтобы, когда конкатенация вызывается нечасто, вы могли бы считать "someString" + someVariable + "someOtherString" более читабельным. Эта статья согласна с вами. - person Steven Jeuris; 23.11.2011