如何为字符串格式提供自定义字符串占位符

31

我有一个字符串

string str ="Enter {0} patient name";

我正在使用 string.format 对其进行格式化。

String.Format(str, "Hello");
现在,如果我想要从某些配置中检索病人信息,那么我需要将 "str" 更改为类似于 "输入 {0} {1} 名称" 的内容。这样它就会用第二个值替换 {1}。问题在于,我想要使用类似于 {pat} 这样的其他格式而不是 {1}。但是当我尝试使用时,它会抛出错误。我需要一个自定义占位符,在运行时可以进行替换,因为有很多文件需要像这样更改(其中可能包含 {0}、{1} 等)。
9个回答

56

您可能想要查看James Newton-KingFormatWith 2.0。它允许您使用属性名称作为格式化标记,例如:

var user = new User()
{
    Name = "Olle Wobbla",
    Age = 25
};

Console.WriteLine("Your name is {Name} and your age is {Age}".FormatWith(user));

您也可以将其与匿名类型一起使用。

更新: Scott Hanselman 也提供了类似的解决方案,但它是在 Object 上实现的扩展方法而不是String

更新2012年:您可以在NuGet.org上获取 Calrius Consulting 的NETFx String.FormatWith Extension Method NuGet 包。

更新2014年:还有StringFormat.NETlittlebit's StringFormat


1
+1. 这很有趣,我以前没见过。看起来它与匿名类型很好地配合使用。 - RichardOD
在相关文章中,一些评论者对性能表示担忧。在使用之前,请确保性能是可接受的。 - Janis Veinbergs
不得不采用Scott的版本,因为FormatWith似乎依赖于System.Web。 - Bryan Legend

21

使用带有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
object[] myInts = new int[] {8,9}; 

然而,你可以采用以下方法来解决问题:
object[] myInts = new string[] { "8", "9" }; 
string bar = string.Format("{0} {1}", myInts); 

1
它会起作用,但问题在于OP不喜欢{0}数字占位符,想要一个更具描述性的。 - Hans Kesting

2
我看到了上面的所有答案,但还是没能理解问题的意思 :)
以下代码为什么不符合您的要求?
string myFirstStr = GetMyFirstStrFromSomewhere();
string mySecondStr = GetMySecondStrFromSomewhere();

string result = "Enter " + myFirstStr + " " + mySecondStr + " name";

通常情况下,这是因为字符串来自于其他地方,比如翻译资源或某种用户可编辑的模板。 - Oylex
这段代码很糟糕,如果字符串更复杂怎么办?你打算连接一堆东西吗? - john cs

2

您还可以使用Marc Gravell的示例并扩展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));

2

我在这里找到了另一个版本: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();
} 

1

你最好使用 Replace 来处理自定义字段,使用 Format 来处理其余部分,例如:

string str = "Enter {0} {pat} name";
String.Format(str.Replace("{pat}", "Patient"), "Hello");

1
var user = new User()
{
    Name = "John",
    Age = 21
};
String result = $"Your name is {user.Name} and your age is {user.Age}";

0
我想要一个更像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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接