2009-06-04 84 views
9

我有一个模板字符串和来自不同来源,但需要匹配以创建一个新的“填写的”字符串参数数组:有没有更好的方法来计算C#中的字符串中的字符串格式占位符?

string templateString = GetTemplate(); // e.g. "Mr {0} has a {1}" 
string[] dataItems = GetDataItems();  // e.g. ["Jones", "ceiling cat"} 

string resultingString = String.Format(templateString, dataItems); 
// e.g. "Mr Jones has a ceiling cat" 

有了这个代码,我假设模板中的字符串格式占位符的数量将等于数据项的数量。对我而言,这通常是一个合理的假设,但我希望能够在没有失败的情况下生成resultingString,即使假设是错误的。我不介意是否有遗漏数据的空白空间。

如果dataItems中的项目太多,String.Format方法可以很好地处理它。如果还不够,我会得到一个异常。

为了解决这个问题,我计算了占位符的数量,并在dataItems数组中添加了新项目(如果没有足够的话)。

计数占位符,我与目前使用的代码是:

private static int CountOccurrences(string haystack) 
{ 
    // Loop through all instances of the string "}". 
    int count = 0; 
    int i = 0; 
    while ((i = text.IndexOf("}", i)) != -1) 
    { 
     i++; 
     count++; 
    } 
    return count; 
} 

显然,这使得假设没有那么不被用于任何形式关闭大括号占位符。这也只是感觉错了。 :)

有没有更好的方法来计算字符串格式的占位符?


许多人正确地指出,我的答案标记为正确不会在许多情况下工作。其主要理由是:

  • 表示,计数占位符的数量不占字面括号({{0}}
  • 计数占位符不考虑重复或跳过占位符的正则表达式(如"{0} has a {1} which also has a {1}"
+0

虽然没有回答你的问题,但这篇文章可能会提供一个替代方案,你可能会觉得有趣http://stackoverflow.com/questions/159017/named-string-formatting-in-c – Kane 2009-06-04 02:30:13

回答

7

合并Damovisa的和乔的回答。 我已经更新了Aydsman的nad activa的评论。

“{0} AA {2} BB {1}”=>计数= 3

“{4} AA {0} BB:

int count = Regex.Matches(templateString, @"(?<!\{)\{([0-9]+).*?\}(?!})") //select all placeholders - placeholder ID as separate group 
       .Cast<Match>() // cast MatchCollection to IEnumerable<Match>, so we can use Linq 
       .Max(m => int.Parse(m.Groups[1].Value)) + 1; // select maximum value of first group (it's a placegolder ID) converted to int 

这种方法将对于像的模板工作{0},{0}” =>计数= 5

“{0} {3},{{7}}”=>数= 4

7

您可以随时使用正则表达式:

using System.Text.RegularExpressions; 
// ... more code 
string templateString = "{0} {2} .{{99}}. {3}"; 
Match match = Regex.Matches(templateString, 
      @"(?<!\{)\{(?<number>[0-9]+).*?\}(?!\})") 
      .Cast<Match>() 
      .OrderBy(m => m.Groups["number"].Value) 
      .LastOrDefault(); 
Console.WriteLine(match.Groups["number"].Value); // Display 3 
+0

谢谢你,我会试一试。 – Damovisa 2009-06-04 02:44:20

+0

作为参考,工作的代码是:int len = new System.Text.RegularExpressions.Regex(“{[0-9] +。*?}”)。Matches(template).Count; – Damovisa 2009-06-04 02:48:15

+0

根据文档,问题是字符{和}在正则表达式中是特殊的:http://msdn.microsoft.com/en-us/library/3206d374.aspx – 2009-06-04 02:56:02

0

你可以使用正则表达式来算{}对,甲肝e只有在它们之间使用的格式。除非使用格式化选项,否则@“\ {\ d + \}”足够好。

3

其实不是你的问题的答案,而是一个可能的解决方案(虽然不是一个完美的优雅);因为string.Format不关心冗余项目,所以可以用string.Empty实例填充dataItems集合。

16

计数的占位符不帮助 - 考虑以下情况:

“{0} ... {1} ... {0}” - 需要2个值

“{1} {3}“ - 需要4个值,其中两个被忽略

第二个示例不是牵强。

例如,你可能有这样的事情在美国英语:

String.Format("{0} {1} {2} has a {3}", firstName, middleName, lastName, animal); 

在某些文化中,也可以不使用中间的名字,你可能有:

String.Format("{0} {2} ... {3}", firstName, middleName, lastName, animal); 

如果你想要做到这一点,你需要寻找的格式说明{索引[,长度] [:formatString中]}的最大指数,忽略重复括号(例如,{{N}})。重复的大括号用于将大括号作为文字插入到输出字符串中。我将编码作为一个练习:) - 但我认为它不能或应该在最常见的情况下使用正则表达式(即使用length和/或formatString)。

即使你不使用长度或formatString的今天,未来的开发人员可能认为这是一个无害的变化增加一个 - 这将是一种耻辱,这打破你的代码。

我会尝试模仿StringBuilder.AppendFormat(它被String.Format调用)中的代码,即使它有点难看 - 使用Lutz Reflector来获取此代码。基本上迭代查找格式说明符的字符串,并获取每个说明符的索引值。

1

由于我没有编辑的权威帖子,我会提出我较短的(和正确的)版本的Marqus的答案:

int num = Regex.Matches(templateString,@"(?<!\{)\{([0-9]+).*?\}(?!})") 
      .Cast<Match>() 
      .Max(m => int.Parse(m.Groups[0].Value)) + 1; 

我使用的是Aydsman提出的正则表达式,但没有经过测试。

2

也许你正在试图破解了牛刀?

为什么不在你对String.Format的调用周围放一个try/catch

这有点难看,但以最少的工作量和最少的测试解决了您的问题,并且保证即使在格式化字符串时还有其他问题(如{{文字,或更复杂的格式字符串,里面还非数字字符:{0:$#,## 0.00;($#,## 0.00);零})

(是的,这意味着你将无法检测更多的数据项比格式说明符,但是这是一个问题?大概你的软件的用户会注意到,他们已经截断了他们的输出并纠正他们的格式字符串?)

3

如果模板中没有占位符,Marqus的答案失败串。

添加的.DefaultIfEmpty()m==null有条件解决此问题的。

Regex.Matches(templateString, @"(?<!\{)\{([0-9]+).*?\}(?!})") 
    .Cast<Match>() 
    .DefaultIfEmpty() 
    .Max(m => m==null?-1:int.Parse(m.Groups[1].Value)) + 1; 
3

有与上面提出的正则表达式的问题,这将匹配“{0}”:

Regex.Matches(templateString, @"(?<!\{)\{([0-9]+).*?\}(?!})") 
... 

寻找收盘时}它使用的问题是*这允许初始}作为匹配。所以改变这个停止在第一个}使后缀检查工作。换句话说,用这个作为正则表达式:

Regex.Matches(templateString, @"(?<!\{)\{([0-9]+)[^\}]*?\}(?!\})") 
... 

我提出在此基础上所有的一对夫妇的静态函数,也许你会发现它们非常有用。

public static class StringFormat 
{ 
    static readonly Regex FormatSpecifierRegex = new Regex(@"(?<!\{)\{([0-9]+)[^\}]*?\}(?!\})", RegexOptions.Compiled); 

    public static IEnumerable<int> EnumerateArgIndexes(string formatString) 
    { 
     return FormatSpecifierRegex.Matches(formatString) 
     .Cast<Match>() 
     .Select(m => int.Parse(m.Groups[1].Value)); 
    } 

    /// <summary> 
    /// Finds all the String.Format data specifiers ({0}, {1}, etc.), and returns the 
    /// highest index plus one (since they are 0-based). This lets you know how many data 
    /// arguments you need to provide to String.Format in an IEnumerable without getting an 
    /// exception - handy if you want to adjust the data at runtime. 
    /// </summary> 
    /// <param name="formatString"></param> 
    /// <returns></returns> 
    public static int GetMinimumArgCount(string formatString) 
    { 
     return EnumerateArgIndexes(formatString).DefaultIfEmpty(-1).Max() + 1; 
    } 

} 
1

非常迟到的问题,但发生在另一个切线。

即使使用单元测试(即缺少参数),String.Format仍然存在问题。开发人员放入错误的位置占位符或格式化的字符串进行编辑,它编译得很好,但在另一个代码位置甚至另一个程序集中使用,并且您在运行时得到FormatException。理想情况下单元测试或集成测试应该抓住这一点。

尽管这不是解决方案,但它是一种解决方法。您可以使 成为接受格式化字​​符串和对象列表(或数组)的帮助器方法。在助手方法中,将列表填充到预定义的固定长度,该长度将超过消息中占位符的数量。例如,下面假设10个占位符就足够了。填充元素可以为null或类似“[Missing]”的字符串。

int q = 123456, r = 76543; 
List<object> args = new List<object>() { q, r};  

string msg = "Sample Message q = {2:0,0} r = {1:0,0}"; 

//Logic inside the helper function 
int upperBound = args.Count; 
int max = 10; 

for (int x = upperBound; x < max; x++) 
{ 
    args.Add(null); //"[No Value]" 
} 
//Return formatted string 
Console.WriteLine(string.Format(msg, args.ToArray())); 

这是理想吗?不,但是对于日志记录或某些用例,它是防止运行时异常的可接受替代方法。你甚至可以用“[No Value]”替换null元素和/或添加数组位置,然后在格式化字符串中测试No Value,然后将其记录为问题。

0

基于this answer和戴维·怀特的回答这里是一个更新版本:

string formatString = "Hello {0:C} Bye {{300}} {0,2} {34}"; 
//string formatString = "Hello"; 
//string formatString = null; 

int n; 
var countOfParams = Regex.Matches(formatString?.Replace("{{", "").Replace("}}", "") ?? "", @"\{([0-9]+)") 
    .OfType<Match>() 
    .DefaultIfEmpty() 
    .Max(m => Int32.TryParse(m?.Groups[1]?.Value, out n) ? n : -1) 
    + 1; 

Console.Write(countOfParams); 

注意事项:

  1. 更换是采取双大括号的护理更直接的方式。这与StringBuilder.AppendFormatHelper如何在内部处理它们相似。
  2. 由于分别被消除 '{{' 和 '}}',正则表达式可以简化为 '{([0-9] +)'
  3. 如果formatString的是空这将甚至工作
  4. 这将工作即使格式无效,请说'{3444444456}'。通常这会导致整数溢出。
相关问题