我需要在程序中创建一个非常长的字符串,并且一直在使用String.Format。我遇到的问题是当你有超过8-10个参数时跟踪所有数字。
是否可以创建某种形式的重载,以接受与此类似的语法?
String.Format("You are {age} years old and your last name is {name} ",
{age = "18", name = "Foo"});
答案 0 :(得分:71)
以下内容如何适用于匿名类型(以下示例)或常规类型(域实体等):
static void Main()
{
string s = Format("You are {age} years old and your last name is {name} ",
new {age = 18, name = "Foo"});
}
使用:
static readonly Regex rePattern = new Regex(
@"(\{+)([^\}]+)(\}+)", RegexOptions.Compiled);
static string Format(string pattern, object template)
{
if (template == null) throw new ArgumentNullException();
Type type = template.GetType();
var cache = new Dictionary<string, string>();
return rePattern.Replace(pattern, match =>
{
int lCount = match.Groups[1].Value.Length,
rCount = match.Groups[3].Value.Length;
if ((lCount % 2) != (rCount % 2)) throw new InvalidOperationException("Unbalanced braces");
string lBrace = lCount == 1 ? "" : new string('{', lCount / 2),
rBrace = rCount == 1 ? "" : new string('}', rCount / 2);
string key = match.Groups[2].Value, value;
if(lCount % 2 == 0) {
value = key;
} else {
if (!cache.TryGetValue(key, out value))
{
var prop = type.GetProperty(key);
if (prop == null)
{
throw new ArgumentException("Not found: " + key, "pattern");
}
value = Convert.ToString(prop.GetValue(template, null));
cache.Add(key, value);
}
}
return lBrace + value + rBrace;
});
}
答案 1 :(得分:2)
不完全相同,但有点欺骗它...使用扩展方法,字典和一些小代码:
像这样...... public static class Extensions {
public static string FormatX(this string format, params KeyValuePair<string, object> [] values) {
string res = format;
foreach (KeyValuePair<string, object> kvp in values) {
res = res.Replace(string.Format("{0}", kvp.Key), kvp.Value.ToString());
}
return res;
}
}
答案 2 :(得分:2)
从C#6开始,现在可以使用新的string interpolation语法进行这种字符串插值:
var formatted = $"You are {age} years old and your last name is {name}";
答案 3 :(得分:1)
原始实施:
public static class StringUtility
{
public static string Format(string pattern, IDictionary<string, object> args)
{
StringBuilder builder = new StringBuilder(pattern);
foreach (var arg in args)
{
builder.Replace("{" + arg.Key + "}", arg.Value.ToString());
}
return builder.ToString();
}
}
用法:
StringUtility.Format("You are {age} years old and your last name is {name} ",
new Dictionary<string, object>() {{"age" = 18, "name" = "Foo"}});
您也可以使用匿名类,但由于您需要反射,因此速度要慢得多。
对于真正的实现,您应该使用正则表达式
答案 4 :(得分:1)
如果age / name是您应用程序中的变量,那该怎么办?因此,您需要一种排序语法,使其几乎与{age_1}一样独特?
如果您在使用8-10个参数时遇到问题:为什么不使用
"You are " + age + " years old and your last name is " + name + "
答案 5 :(得分:0)
虽然C#6.0现在可以使用字符串插值来完成此操作,但有时需要在运行时使用动态格式字符串执行此操作。我已经无法使用其他需要DataBinder.Eval的方法,因为它们在.NET Core中不可用,并且对Regex解决方案的性能不满意。
考虑到这一点,这是我写的一个基于状态机的免费状态机解析器。当输入包含不平衡的大括号和/或其他错误时,它处理无限级别的{{{escaping}}}
并抛出FormatException
。虽然main方法采用Dictionary<string, object>
,但辅助方法也可以采用object
并通过反射使用其参数。
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
。
编辑:
上的完整项目