Форматирование именованных строк в 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}", ...);
Было бы неплохо сделать это, используя имя переменной, но допустим и словарь.
18 ответов
Для этого не существует встроенного метода.
string myString = "{foo} is {bar} and {yadi} is {yada}".Inject(o);
Status.Text = "{UserName} last logged in at {LastLoginDate}".FormatWith(user);
Третий улучшенный метод, частично основанный на двух выше, от Фила Хаака
У меня есть реализация, которую я только что разместил в своем блоге здесь: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx
Это решает некоторые проблемы, которые эти другие реализации имеют с выходом скобки. У поста есть подробности. Это тоже делает DataBinder.Eval, но все еще очень быстро.
Интерполированные строки были добавлены в C# 6.0 и Visual Basic 14
Оба были представлены через новый компилятор Roslyn в Visual Studio 2015.
C# 6.0:
return "\{someVariable} and also \{someOtherVariable}"
ИЛИ ЖЕreturn $"{someVariable} and also {someOtherVariable}"
Источник: что нового в C# 6.0
VB 14:
return $"{someVariable} and also {someOtherVariable}"
- источник: что нового в VB 14
Интересные функции (в Visual Studio 2015 IDE):
- поддерживаетсясинтаксическая раскраска - переменные, содержащиеся в строках, подсвечиваются
- поддерживаетсярефакторинг - при переименовании переменные, содержащиеся в строках, тоже переименовываются
- на самом деле поддерживаются не только имена переменных, но и выражения - например, не только
{index}
работает, но также{(index + 1).ToString().Trim()}
Наслаждайтесь! (и нажмите "Отправить улыбку" в VS)
Вы также можете использовать анонимные типы, как это:
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" })
Кажется, нет способа сделать это из коробки. Тем не менее, это выглядит возможным реализовать свой собственный 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 имеет 2 типа цитат
Предостережение в том, что вы не можете смешивать FormatProviders
таким образом, необычное форматирование текста не может быть использовано одновременно.
Сам фреймворк не предоставляет способа сделать это, но вы можете взглянуть на этот пост Скотта Хансельмана. Пример использования:
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 });
См. /questions/28550209/kakie-vashi-lyubimyie-metodyi-rasshireniya-dlya-c-codeplexcomextensionoverflow
С помощью связанного расширения вы можете написать это:
var str = "{foo} {bar} {baz}".Format(foo=>"foo", bar=>2, baz=>new object());
и вы получите "foo 2 System.Object
".
Я думаю, что ближе всего вы получите индексированный формат:
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
replacements.ForEach(delegate(KeyValuePair<string,string>) item) { MyString = MyString.Replace(item.Key, item.Value);});
Лямбда была бы еще проще, но я все еще на.Net 2.0. Также обратите внимание, что производительность.Replace () не является звездной при многократном использовании, поскольку строки в.Net являются неизменяемыми. Кроме того, это требует MyString
переменная должна быть определена таким образом, чтобы она была доступна делегату, поэтому она еще не идеальна.
Моя библиотека с открытым исходным кодом, Regextra, поддерживает именованное форматирование (среди прочего). В настоящее время он нацелен на.NET 4.0+ и доступен на NuGet. У меня также есть вступительное сообщение в блоге об этом: Regextra: помогает вам уменьшить ваши (проблемы) {2}.
Именованный бит форматирования поддерживает:
- Основное форматирование
- Форматирование вложенных свойств
- Форматирование словаря
- Экранирование разделителей
- Стандартное / Пользовательское /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 (выше) и вики для других примеров.
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,今天是2011-05-04, 这是你第18次 次 ,积分{ 100.40 }
Проверьте это:
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));
Производительность довольно хорошая по сравнению с другими решениями.
Я сомневаюсь, что это будет возможно. Первое, что приходит на ум, это как получить доступ к именам локальных переменных?
Однако для этого можно использовать какой-нибудь умный способ использования выражений LINQ и Lambda.
Вот простой метод для любого объекта:
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
Вот один, который я сделал некоторое время назад. Он расширяет 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;
}
}
}
Я решил это немного иначе, чем существующие решения. Он выполняет ядро замены названного элемента (а не бит отражения, который сделали некоторые). Это очень быстро и просто... Это мое решение:
/// <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!"));
}
Надеюсь, кто-то найдет это полезным!
Несмотря на то, что принятый ответ дает несколько хороших примеров,.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
возвращается
Я реализовал это простой класс, который дублирует функциональность String.Format (кроме случаев использования классов). Вы можете использовать словарь или тип для определения полей.
https://github.com/SergueiFedorov/NamedFormatString
C# 6.0 добавляет эту функциональность прямо в спецификацию языка, поэтому NamedFormatString
для обратной совместимости.
string language = "Python";
int numquotes = 2;
string output = language + " has "+ numquotes + " language types.";
Изменить: то, что я должен был сказать, было: "Нет, я не верю, что то, что вы хотите сделать, поддерживается C#. Это настолько близко, насколько вы собираетесь получить".