2009-06-04 80 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}'。通常這會導致整數溢出。