使用花括号的String.Format函数

7

我们的低级别日志记录库必须应对各种发送给它的日志消息。

其中一些消息包括花括号(作为文本的一部分),而有些包含要使用String.Format格式化为字符串的参数

例如,这个字符串可以是Logger类的输入:

“Parameter: {Hostname} Value: {0}” 正确的变量将用于格式化器。

为了正确执行此操作,我必须转义不属于格式化的花括号(通过加倍)。

我想使用正则表达式来解决它,但这并不像看起来那么简单,因为我不知道如何匹配花括号内的这些字符串(不是由String.Format用于格式化目的的字符串)。

另一个问题是Logger类应尽可能高效地运行,开始将正则表达式作为其操作的一部分可能会影响性能。

这方面是否有适当且已知的最佳实践?


你已经在说你要把它们加倍(像这样:{{Hostname}})。为什么不起作用? - Steven
我并没有说我要加倍。我是说我必须转义(双重)与格式无关的内容,或者采取其他措施来防止这个问题。 - lysergic-acid
4个回答

4

使用一个正则表达式完成:

string input = "Parameter: {Hostname} Value: {0}";
input = Regex.Replace(input, @"{([^[0-9]+)}", @"{{$1}}");
Console.WriteLine(input);

输出:

参数: {{Hostname}} 值: {0}

当然,只要没有包含数字但仍应使用{{ }}进行转义的参数,此方法就可以正常工作。


非常好,我会进行性能分析以查看我们实际上对性能造成了多少影响,但看起来很不错。 - lysergic-acid
尝试在一个包含“P:{Host} Val:{4}”重复的100 kB文件上进行测试,大约需要12毫秒。当然,你的情况可能会有所不同,但我无法想到更简洁的方法来加快速度。 - Anders Arpi
这将持续到当前记录器重新设计。非常感谢,我不太确定正则表达式的所有匹配细节,需要加强该领域。再次感谢。 - lysergic-acid
1
如果花括号在一行打开,然后在另一行关闭,例如跨越多行的函数,则无法正常工作。 - TruMan1

2

我认为你应该查看日志记录器接口。与 Console.WriteLine 的工作方式进行比较:

  • Console.WriteLine(String) 精确输出给定的字符串,没有格式化,也没有特殊处理 { 和 }。
  • Console.WriteLine(String, Object[]) 使用格式化输出。{ 和 } 是特殊字符,调用者必须转义为 {{ 和 }}。

我认为这种设计有缺陷,需要区分代码中不同的花括号出现次数以找出意思。将在输出中出现的 { 转义为 {{,减轻转义负担。


这是非常合理的。我们在两者之间有一个分离,但我不认为在这种情况下把责任放在调用者身上是可能的,因为我无法强制执行此策略(我无法强制记录器的用户自己转义字符串)。 - lysergic-acid
如果假定用户足够熟练,能够理解 {0} 是一种格式选项,那么可以安全地假定用户也能将 { 转义为 {{。我认为最好遵循其他格式化感知 API 使用的原则,而不是发明一些特殊的东西。 - Anders Abel
1
我同意Anders的观点。如果调用者想要格式化,他们应该自己进行转义。作为一种折中方案,你可以按照Anders的建议实现一个Log(string)、Log(string, params object[]),并提供一个Log(bool, string, object[])来进行正则表达式解析——但是请使用[Obsolete]属性标记以防止其被使用。 - Grhm

2

我会把所有花括号都加倍,然后用类似于{{\d+}}的正则表达式查找并替换它们,以便它们可以恢复到其原始格式 -- {{0}} => {0} -- 在你的字符串中。
因此,对于每一行,我会像这样做:

string s = input.Replace("{", "{{").Replace("}", "}}");
return Regex.Replace(s, @"{{(?<val>\d+)}}", 
                     m => { return "{" + m.Groups["val"] + "}"; }));

这是对原问题的技术性回答,但@Anders Abel说得很对。重新考虑设计可能是值得的...


这也无法识别包含格式化信息的格式占位符,例如“FourDigitHexValue={0:x4} Date={0:dd/MM/yyyy}”。有关更多示例,请参见http://blog.stevex.net/string-formatting-in-csharp/。 - Grhm
@Grmh 当然。这个答案只是一个快速的解决方法,因为它取决于发送到日志记录器的输入。但是正如Anders Abel所说的那样,与其继续使用更复杂的正则表达式,不如进行另一次设计审查。 - pierroz

2

为了让调用者拥有格式化字符串并处理格式说明符,例如:

Logger.Log("{0:dd/mm/yyy} {0:hh:mm:ss} {hostname} 某个错误{1:x4}发生在{123Component}上!", DateTime.UtcNow, 257)

你需要一个像这样的正则表达式:

string input = "{0:dd/mm/yyy} {0:hh:mm:ss} {hostname} Some error {1:x4} happened on {123Component}!";
Regex reg = new Regex(@"(\{[^[0-9}]+?[^}]*\}|\{(?![0-9]+:)[^}]+?\})");
string output = reg.Replace(input, "{$1}");
Console.WriteLine(output);

这将输出:
"{0:dd/mm/yyy} {0:hh:mm:ss} {{hostname}} Some error {1:x4} happened on {{123Component}}!"

再次强调,我同意Anders Abel的观点,你应该重新设计以避免需要日志库来完成这个任务。


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