代码之家  ›  专栏  ›  技术社区  ›  Damovisa

在C#中,是否有更好的方法来计算字符串中的字符串格式占位符?

  •  15
  • Damovisa  · 技术社区  · 17 年前

    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}" )
    12 回复  |  直到 17 年前
        1
  •  18
  •   to StackOverflow    17 年前

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

    例如,在美国英语中,你可能有这样的东西:

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

    在某些文化中,可能不使用中间名,您可能有:

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

    如果你想这样做,你需要寻找格式说明符

    即使你今天没有使用length或formatString,未来的开发人员可能会认为添加一个是无害的更改——这会破坏你的代码,这将是一种耻辱。

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

        2
  •  10
  •   MarekBaron    17 年前

    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
    

    这种方法适用于以下模板:

    “{4}aa{0}bb{0},{0}”=>计数=5

        3
  •  8
  •   Paulo Santos    17 年前

    您始终可以使用Regex:

    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
    
        4
  •  3
  •   jerryjvl    17 年前

    dataItems 集合与一些 string.Empty 实例,因为 string.Format

        5
  •  3
  •   Jason Williams    17 年前

    也许你正试图用大锤敲碎一颗坚果?

    围绕您对String.Format的调用。

    它有点难看,但以一种需要最少努力、最少测试的方式解决了你的问题,并且即使有你没有考虑的其他关于格式化字符串的东西(比如{{文字,或其中包含非数字字符的更复杂的格式字符串:{0:$#,##0.00;($#,###.00);零})也能保证有效

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

        6
  •  3
  •   David White    14 年前

    如果模板字符串中没有占位符,Marques的回答将失败。

    添加 .DefaultIfEmpty() 和 m==null

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

    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;
        }
    
    }
    
        8
  •  3
  •   Branko Dimitrijevic    7 年前

    你可以“滥用” ICustomFormatter

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace FormatPlaceholders {
    
        class Program {
    
            class FormatSnooper : IFormatProvider, ICustomFormatter {
    
                public object GetFormat(Type formatType) {
                    return this;
                }
    
                public string Format(string format, object arg, IFormatProvider formatProvider) {
                    Placeholders.Add(((int)arg, format));
                    return null;
                }
    
                internal readonly List<(int index, string format)> Placeholders = new List<(int index, string format)>();
    
            }
    
            public static IEnumerable<(int index, string format)> GetFormatPlaceholders(string format, int max_count = 100) {
    
                var snooper = new FormatSnooper();
    
                string.Format(
                    snooper,
                    format,
                    Enumerable.Range(0, max_count).Cast<object>().ToArray()
                );
    
                return snooper.Placeholders;
    
            }
    
            static void Main(string[] args) {
                foreach (var (index, format) in GetFormatPlaceholders("{1:foo}{4:bar}{1:baz}"))
                    Console.WriteLine($"{index}: {format}");
            }
    
        }
    
    }
    

    打印内容:

    1: foo
    4: bar
    1: baz
    

    index ,计数,找到“洞”等等。。。


    我意识到我迟到了(几年),但我需要一些与OP要求类似的东西,所以我分享了我在这里提出的解决方案,以防有人发现它有用。..

        9
  •  1
  •   Philippe Leybaert    17 年前

    由于我没有编辑帖子的权限,我将提出Marques答案的简短(和正确)版本:

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

        10
  •  1
  •   Charles Byrne    10 年前

    虽然这不是答案的解决方案,但它是一种变通方法。你可以做

    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()));
    

        11
  •  1
  •   Anton Krouglov    8 年前

    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}”,这也会起作用。通常这会导致整数溢出。
        12
  •  0
  •   John Fisher    17 年前