Как предоставить пользовательский строковый заполнитель для строкового формата
У меня есть строка
string str ="Enter {0} patient name";
Я использую string.format для его форматирования.
String.Format(str, "Hello");
Теперь, если я хочу, чтобы пациент также был извлечен из некоторой конфигурации, мне нужно изменить str на что-то вроде
"Enter {0} {1} name"
. Поэтому он заменит {1} на второе значение. Проблема в том, что я хочу вместо {1} какой-то другой формат что-то вроде {pat}
. Но когда я пытаюсь использовать, он выдает ошибку. Причина, по которой я хочу получить другой формат, - это то, что мне нужно изменить такие файлы (которые могут содержать {0}, {1} и т.д.). Поэтому мне нужен пользовательский заполнитель, который можно заменить во время выполнения.
Ответы
Ответ 1
Возможно, вы захотите проверить FormatWith 2.0 Джеймс Ньютон-Кинг. Он позволяет использовать имена свойств в качестве форматирующих токенов, таких как:
var user = new User()
{
Name = "Olle Wobbla",
Age = 25
};
Console.WriteLine("Your name is {Name} and your age is {Age}".FormatWith(user));
Вы также можете использовать его с анонимными типами.
UPDATE: Существует также решение Scott Hanselman, но он реализован как набор методов расширения на Object
вместо String
.
ОБНОВЛЕНИЕ 2012. Вы можете получить Calrius Consulting NETFx String.FormatWith Extension Method Пакет NuGet на NuGet.org
UPDATE 2014. Существует также StringFormat.NET и littlebit StringFormat
Ответ 2
Regex
с MatchEvaluator
кажется хорошим вариантом:
static readonly Regex re = new Regex(@"\{([^\}]+)\}", RegexOptions.Compiled);
static void Main()
{
string input = "this {foo} is now {bar}.";
StringDictionary fields = new StringDictionary();
fields.Add("foo", "code");
fields.Add("bar", "working");
string output = re.Replace(input, delegate (Match match) {
return fields[match.Groups[1].Value];
});
Console.WriteLine(output); // "this code is now working."
}
Ответ 3
Я видел все ответы выше, но не мог правильно задать вопрос.
Есть ли какая-то особая причина, почему следующий код не соответствует вашим требованиям?
string myFirstStr = GetMyFirstStrFromSomewhere();
string mySecondStr = GetMySecondStrFromSomewhere();
string result = "Enter " + myFirstStr + " " + mySecondStr + " name";
Ответ 4
object[] myInts = new int[] {8,9};
Однако вы можете сойти с рук:
object[] myInts = new string[] { "8", "9" };
string bar = string.Format("{0} {1}", myInts);
Ответ 5
Вот еще одна версия этого, которую я нашел здесь: http://www.reddit.com/r/programming/comments/bodml/beef_up_params_in_c_5_to_solve_lambda_abuse/c0nrsf1
Любое решение для этого будет включать отражение, которое является менее идеальным, но здесь его код с некоторыми другими важными проблемами производительности разрешен. (Нет проверки ошибок. Добавьте его, если хотите.):
1) Использует прямое отражение во время выполнения, нет служебных данных DataBinder
2) Не использует регулярные выражения, использует однопроходный анализ и состояние.
3) Не преобразовывает строку в промежуточную строку и затем снова конвертирует ее в окончательный формат.
4) Выделяет и объединяет один StringBuilder вместо того, чтобы создавать новые строки по всему месту и объединять их в новые строки.
5) Удаляет служебные данные стека для вызова делегата для n операций замены.
6) В общем, это один проход, который будет масштабироваться относительно линейно (все же некоторые затраты для каждого поиска прокрутки и поиска вложенных опор, но это.)
public static string FormatWith(this string format, object source)
{
StringBuilder sbResult = new StringBuilder(format.Length);
StringBuilder sbCurrentTerm = new StringBuilder();
char[] formatChars = format.ToCharArray();
bool inTerm = false;
object currentPropValue = source;
for (int i = 0; i < format.Length; i++)
{
if (formatChars[i] == '{')
inTerm = true;
else if (formatChars[i] == '}')
{
PropertyInfo pi = currentPropValue.GetType().GetProperty(sbCurrentTerm.ToString());
sbResult.Append((string)(pi.PropertyType.GetMethod("ToString", new Type[]{}).Invoke(pi.GetValue(currentPropValue, null), null)));
sbCurrentTerm.Clear();
inTerm = false;
currentPropValue = source;
}
else if (inTerm)
{
if (formatChars[i] == '.')
{
PropertyInfo pi = currentPropValue.GetType().GetProperty(sbCurrentTerm.ToString());
currentPropValue = pi.GetValue(source, null);
sbCurrentTerm.Clear();
}
else
sbCurrentTerm.Append(formatChars[i]);
}
else
sbResult.Append(formatChars[i]);
}
return sbResult.ToString();
}
Ответ 6
Вам, вероятно, лучше использовать Replace для настраиваемого поля и Format для остальных, например:
string str = "Enter {0} {pat} name";
String.Format(str.Replace("{pat}", "Patient"), "Hello");
Ответ 7
Вы также можете использовать пример из класса Marc Gravell и Extend the String:
public static class StringExtension
{
static readonly Regex re = new Regex(@"\{([^\}]+)\}", RegexOptions.Compiled);
public static string FormatPlaceholder(this string str, Dictionary<string, string> fields)
{
if (fields == null)
return str;
return re.Replace(str, delegate(Match match)
{
return fields[match.Groups[1].Value];
});
}
}
Пример использования:
String str = "I bought a {color} car";
Dictionary<string, string> fields = new Dictionary<string, string>();
fields.Add("color", "blue");
str.FormatPlaceholder(fields));
Ответ 8
Я хотел что-то, что больше походило на форматирование строки Python, поэтому я написал следующее:
https://gist.github.com/samwyse/b225b32ae1aea6fb27ad9c966b9ca90b
Используйте его следующим образом:
Dim template = New FormatFromDictionary("{cat} vs {dog}")
Dim d = New Dictionary(Of String, Object) From {
{"cat", "Felix"}, {"dog", "Rex"}}
Console.WriteLine(template.Replace(d)) ' Felix vs Rex