关于.net 3.5:C#中的命名字符串格式

Named string formatting in C#

是否有任何方法可以按名称而不是C中的位置来格式化字符串?

在Python中,我可以这样做(无耻地从这里被盗):

1
2
3
>>> print '%(language)s has %(#)03d quote types.' % \
      {'language':"Python","#": 2}
Python has 002 quote types.

用C语言有什么方法可以做到这一点吗?比如说:

1
String.Format("{some_variable}: {some_other_variable}", ...);

使用变量名这样做是很好的,但是字典也是可以接受的。


没有处理此问题的内置方法。

这里有一个方法

1
string myString ="{foo} is {bar} and {yadi} is {yada}".Inject(o);

这是另一个

1
Status.Text ="{UserName} last logged in at {LastLoginDate}".FormatWith(user);

第三种改进方法,部分基于上述两种方法,来自Phil Haack。


我刚在博客上发布了一个实现:http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-analysis-and-edge-cases.aspx

它解决了这些其他实现在使用大括号转义时遇到的一些问题。这篇文章有细节。它可以做数据绑定器,也可以进行评估,但是仍然很快。


在C 6.0和Visual Basic 14中添加了内插字符串。

两者都是通过新的Roslyn编译器在Visual Studio 2015中引入的。

  • C 6.0:

    return"\{someVariable} and also \{someOtherVariable}"或江户十一〔一〕号

    • 资料来源:C 6.0的新功能

      nbsp;

  • VB 14:

    江户十一〔一〕号

    • 资料来源:vb 14的新功能

值得注意的功能(在Visual Studio 2015 IDE中):

  • 支持语法着色-突出显示字符串中包含的变量
  • 支持重构-重命名时,字符串中包含的变量也将被重命名
  • 实际上,不仅支持变量名,而且支持表达式-例如,不仅{index}有效,而且{(index + 1).ToString().Trim()}也有效。

享受吧!(在vs中单击"发送微笑")。


您还可以使用类似以下匿名类型:

1
2
3
4
5
6
7
    public string Format(string input, object p)
    {
        foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(p))
            input = input.Replace("{" + prop.Name +"}", (prop.GetValue(p) ??"(null)").ToString());

        return input;
    }

当然,如果您还想解析格式,就需要更多的代码,但是您可以使用如下函数格式化字符串:

1
Format("test {first} and {another}", new { first ="something", another ="something else" })


似乎没有现成的方法可以做到这一点。不过,实现您自己的IFormatProvider似乎是可行的,它为值链接到IDictionary

1
2
3
4
5
6
7
8
var Stuff = new Dictionary<string, object> {
   {"language","Python" },
   {"#", 2 }
};
var Formatter = new DictionaryFormatProvider();

// Interpret {0:x} where {0}=IDictionary and"x" is hash key
Console.WriteLine string.Format(Formatter,"{0:language} has {0:#} quote types", Stuff);

输出:

1
Python has 2 quote types

警告是不能混合使用FormatProviders,因此不能同时使用花哨的文本格式。


框架本身并没有提供这样做的方法,但是您可以看看ScottHanselman的文章。示例用法:

1
2
3
Person p = new Person();  
string foo = p.ToString("{Money:C} {LastName}, {ScottName} {BirthDate}");  
Assert.AreEqual("$3.43 Hanselman, {ScottName} 1/22/1974 12:00:00 AM", foo);

这段由詹姆斯·牛顿·金编写的代码是相似的,并且与子属性和索引一起工作。

1
string foo ="Top result for {Name} was {Results[0].Name}".FormatWith(student));

james的代码依赖于system.web.ui.databinder来解析字符串,并且需要引用system.web,有些人不喜欢在非web应用程序中这样做。

编辑:哦,如果你没有一个对象的属性准备好了,它们可以很好地处理匿名类型:

1
2
3
string name = ...;
DateTime date = ...;
string foo ="{Name} - {Birthday}".FormatWith(new { Name = name, Birthday = date });


请参阅https://stackoverflow.com/questions/271398?页码=2

通过"链接到扩展名",您可以编写以下内容:

1
var str ="{foo} {bar} {baz}".Format(foo=>"foo", bar=>2, baz=>new object());

你就可以得到"foo 2 System.Object


我认为最接近的是索引格式:

1
String.Format("{0} has {1} quote types.","C#","1");

还有string.replace(),如果您愿意多步骤执行,并相信在字符串中的任何其他地方都找不到您的"变量",请执行以下操作:

1
2
string MyString ="{language} has {n} quote types.";
MyString = MyString.Replace("{language}","C#").Replace("{n}","1");

展开此项以使用列表:

1
2
3
4
5
List<KeyValuePair<string, string>> replacements = GetFormatDictionary();  
foreach (KeyValuePair<string, string> item in replacements)
{
    MyString = MyString.Replace(item.Key, item.Value);
}

您也可以使用字典迭代它的.keys集合,但通过使用list>,我们可以利用list的.foreach()方法并将其压缩为一行程序:

1
replacements.ForEach(delegate(KeyValuePair<string,string>) item) { MyString = MyString.Replace(item.Key, item.Value);});

lambda会更简单,但我仍然使用.NET 2.0。还要注意,.replace()的性能在迭代使用时并不出色,因为.NET中的字符串是不可变的。此外,这要求MyString变量的定义方式必须使委托能够访问,因此它还不完美。


我的开源库regextra支持命名格式(除其他外)。它目前以.NET 4.0+为目标,可在Nuget上使用。我也有一篇关于它的介绍性博客文章:RegExtra:帮助你减少(问题)2。

命名格式位支持:

  • 基本格式
  • 嵌套属性格式
  • 字典格式
  • 分隔符的转义
  • 标准/自定义/IFormatProvider字符串格式

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var order = new
{
    Description ="Widget",
    OrderDate = DateTime.Now,
    Details = new
    {
        UnitPrice = 1500
    }
};

string template ="We just shipped your order of '{Description}', placed on {OrderDate:d}. Your {{credit}} card will be billed {Details.UnitPrice:C}.";

string result = Template.Format(template, order);
// or use the extension: template.FormatTemplate(order);

结果:

We just shipped your order of 'Widget', placed on 2/28/2014. Your {credit} card will be billed $1,500.00.

查看项目的github链接(上面)和wiki以获取其他示例。


检查这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static string StringFormat(string format, object source)
{
    var matches = Regex.Matches(format, @"\{(.+?)\}");
    List<string> keys = (from Match matche in matches select matche.Groups[1].Value).ToList();

    return keys.Aggregate(
        format,
        (current, key) =>
        {
            int colonIndex = key.IndexOf(':');
            return current.Replace(
               "{" + key +"}",
                colonIndex > 0
                    ? DataBinder.Eval(source, key.Substring(0, colonIndex),"{0:" + key.Substring(colonIndex + 1) +"}")
                    : DataBinder.Eval(source, key).ToString());
        });
}

样品:

1
2
3
string format ="{foo} is a {bar} is a {baz} is a {qux:#.#} is a really big {fizzle}";
var o = new { foo = 123, bar = true, baz ="this is a test", qux = 123.45, fizzle = DateTime.Now };
Console.WriteLine(StringFormat(format, o));

与其他解决方案相比,性能相当不错。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private static Regex s_NamedFormatRegex = new Regex(@"\{(?!\{)(?<key>[\w]+)(:(?<fmt>(\{\{|\}\}|[^\{\}])*)?)?\}", RegexOptions.Compiled);

public static StringBuilder AppendNamedFormat(this StringBuilder builder,IFormatProvider provider, string format, IDictionary<string, object> args)
{
    if (builder == null) throw new ArgumentNullException("builder");
    var str = s_NamedFormatRegex.Replace(format, (mt) => {
        string key = mt.Groups["key"].Value;
        string fmt = mt.Groups["fmt"].Value;
        object value = null;
        if (args.TryGetValue(key,out value)) {
            return string.Format(provider,"{0:" + fmt +"}", value);
        } else {
            return mt.Value;
        }
    });
    builder.Append(str);
    return builder;
}

public static StringBuilder AppendNamedFormat(this StringBuilder builder, string format, IDictionary<string, object> args)
{
    if (builder == null) throw new ArgumentNullException("builder");
    return builder.AppendNamedFormat(null, format, args);
}

例子:

1
2
3
4
5
6
7
8
9
var builder = new StringBuilder();
builder.AppendNamedFormat(
@"你好,{Name},今天是{Date:yyyy/MM/dd}, 这是你第{LoginTimes}次登录,积分{Score:{{ 0.00 }}}",
new Dictionary<string, object>() {
    {"Name","wayjet" },
    {"LoginTimes",18 },
    {"Score", 100.4 },
    {"Date",DateTime.Now }
});

输出:你好,Wayjet,今天是2011-05-04,这是你第18次登录,积分100.40


以下是任何对象的简单方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    using System.Text.RegularExpressions;
    using System.ComponentModel;

    public static string StringWithFormat(string format, object args)
    {
        Regex r = new Regex(@"\{([A-Za-z0-9_]+)\}");

        MatchCollection m = r.Matches(format);

        var properties = TypeDescriptor.GetProperties(args);

        foreach (Match item in m)
        {
            try
            {
                string propertyName = item.Groups[1].Value;
                format = format.Replace(item.Value, properties[propertyName].GetValue(args).ToString());
            }
            catch
            {
                throw new FormatException("The format string is not valid");
            }
        }

        return format;
    }

下面介绍如何使用它:

1
2
 DateTime date = DateTime.Now;
 string dateString = StringWithFormat("{Month}/{Day}/{Year}", date);

产量:2012年2月27日


我怀疑这是可能的。首先想到的是如何访问局部变量名?

然而,使用LINQ和lambda表达式来实现这一点可能有一些聪明的方法。


这是我以前做的。它使用一个采用单个参数的格式方法扩展字符串。好的是它将使用标准字符串.format,如果您提供一个像int这样的简单参数,但是如果您使用类似匿名类型的参数,它也会起作用。

示例用法:

1
"The {Name} family has {Children} children".Format(new { Children = 4, Name ="Smith" })

会导致"史密斯家有4个孩子"。

它不会做疯狂的绑定工作,比如数组和索引器。但它是超简单和高性能的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
    public static class AdvancedFormatString
{

    /// <summary>
    /// An advanced version of string.Format.  If you pass a primitive object (string, int, etc), it acts like the regular string.Format.  If you pass an anonmymous type, you can name the paramters by property name.
    /// </summary>
    /// <param name="formatString"></param>
    /// <param name="arg"></param>
    /// <returns></returns>
    /// <example>
    ///"The {Name} family has {Children} children".Format(new { Children = 4, Name ="Smith" })
    ///
    /// results in
    ///"This Smith family has 4 children
    /// </example>
    public static string Format(this string formatString, object arg, IFormatProvider format = null)
    {
        if (arg == null)
            return formatString;

        var type = arg.GetType();
        if (Type.GetTypeCode(type) != TypeCode.Object || type.IsPrimitive)
            return string.Format(format, formatString, arg);

        var properties = TypeDescriptor.GetProperties(arg);
        return formatString.Format((property) =>
            {
                var value = properties[property].GetValue(arg);
                return Convert.ToString(value, format);
            });
    }


    public static string Format(this string formatString, Func<string, string> formatFragmentHandler)
    {
        if (string.IsNullOrEmpty(formatString))
            return formatString;
        Fragment[] fragments = GetParsedFragments(formatString);
        if (fragments == null || fragments.Length == 0)
            return formatString;

        return string.Join(string.Empty, fragments.Select(fragment =>
            {
                if (fragment.Type == FragmentType.Literal)
                    return fragment.Value;
                else
                    return formatFragmentHandler(fragment.Value);
            }).ToArray());
    }


    private static Fragment[] GetParsedFragments(string formatString)
    {
        Fragment[] fragments;
        if ( parsedStrings.TryGetValue(formatString, out fragments) )
        {
            return fragments;
        }
        lock (parsedStringsLock)
        {
            if ( !parsedStrings.TryGetValue(formatString, out fragments) )
            {
                fragments = Parse(formatString);
                parsedStrings.Add(formatString, fragments);
            }
        }
        return fragments;
    }

    private static Object parsedStringsLock = new Object();
    private static Dictionary<string,Fragment[]> parsedStrings = new Dictionary<string,Fragment[]>(StringComparer.Ordinal);

    const char OpeningDelimiter = '{';
    const char ClosingDelimiter = '}';

    /// <summary>
    /// Parses the given format string into a list of fragments.
    /// </summary>
    /// <param name="format"></param>
    /// <returns></returns>
    static Fragment[] Parse(string format)
    {
        int lastCharIndex = format.Length - 1;
        int currFragEndIndex;
        Fragment currFrag = ParseFragment(format, 0, out currFragEndIndex);

        if (currFragEndIndex == lastCharIndex)
        {
            return new Fragment[] { currFrag };
        }

        List<Fragment> fragments = new List<Fragment>();
        while (true)
        {
            fragments.Add(currFrag);
            if (currFragEndIndex == lastCharIndex)
            {
                break;
            }
            currFrag = ParseFragment(format, currFragEndIndex + 1, out currFragEndIndex);
        }
        return fragments.ToArray();

    }

    /// <summary>
    /// Finds the next delimiter from the starting index.
    /// </summary>
    static Fragment ParseFragment(string format, int startIndex, out int fragmentEndIndex)
    {
        bool foundEscapedDelimiter = false;
        FragmentType type = FragmentType.Literal;

        int numChars = format.Length;
        for (int i = startIndex; i < numChars; i++)
        {
            char currChar = format[i];
            bool isOpenBrace = currChar == OpeningDelimiter;
            bool isCloseBrace = isOpenBrace ? false : currChar == ClosingDelimiter;

            if (!isOpenBrace && !isCloseBrace)
            {
                continue;
            }
            else if (i < (numChars - 1) && format[i + 1] == currChar)
            {//{{ or }}
                i++;
                foundEscapedDelimiter = true;
            }
            else if (isOpenBrace)
            {
                if (i == startIndex)
                {
                    type = FragmentType.FormatItem;
                }
                else
                {

                    if (type == FragmentType.FormatItem)
                        throw new FormatException("Two consequtive unescaped { format item openers were found.  Either close the first or escape any literals with another {.");

                    //curr character is the opening of a new format item.  so we close this literal out
                    string literal = format.Substring(startIndex, i - startIndex);
                    if (foundEscapedDelimiter)
                        literal = ReplaceEscapes(literal);

                    fragmentEndIndex = i - 1;
                    return new Fragment(FragmentType.Literal, literal);
                }
            }
            else
            {//close bracket
                if (i == startIndex || type == FragmentType.Literal)
                    throw new FormatException("A } closing brace existed without an opening { brace.");

                string formatItem = format.Substring(startIndex + 1, i - startIndex - 1);
                if (foundEscapedDelimiter)
                    formatItem = ReplaceEscapes(formatItem);//a format item with a { or } in its name is crazy but it could be done
                fragmentEndIndex = i;
                return new Fragment(FragmentType.FormatItem, formatItem);
            }
        }

        if (type == FragmentType.FormatItem)
            throw new FormatException("A format item was opened with { but was never closed.");

        fragmentEndIndex = numChars - 1;
        string literalValue = format.Substring(startIndex);
        if (foundEscapedDelimiter)
            literalValue = ReplaceEscapes(literalValue);

        return new Fragment(FragmentType.Literal, literalValue);

    }

    /// <summary>
    /// Replaces escaped brackets, turning '{{' and '}}' into '{' and '}', respectively.
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    static string ReplaceEscapes(string value)
    {
        return value.Replace("{{","{").Replace("}}","}");
    }

    private enum FragmentType
    {
        Literal,
        FormatItem
    }

    private class Fragment
    {

        public Fragment(FragmentType type, string value)
        {
            Type = type;
            Value = value;
        }

        public FragmentType Type
        {
            get;
            private set;
        }

        /// <summary>
        /// The literal value, or the name of the fragment, depending on fragment type.
        /// </summary>
        public string Value
        {
            get;
            private set;
        }


    }

}


尽管公认的答案给出了一些很好的例子,但是.inject和一些haack示例并不处理转义。许多人还严重依赖于regex(较慢)或databinder.eval,它在.NET核心和其他一些环境中不可用。

考虑到这一点,我已经编写了一个简单的基于状态机的解析器,它可以通过字符进行流式处理,并逐个字符地写入StringBuilder输出。它被实现为String扩展方法,可以同时使用参数作为输入(使用反射)的Dictionaryobject

当输入包含不平衡的大括号和/或其他错误时,它处理{{{escaping}}}的无限级别,并抛出FormatException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
public static class StringExtension {
    /// <summary>
    /// Extension method that replaces keys in a string with the values of matching object properties.
    /// </summary>
    /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
    /// <param name="injectionObject">The object whose properties should be injected in the string</param>
    /// <returns>A version of the formatString string with keys replaced by (formatted) key values.</returns>
    public static string FormatWith(this string formatString, object injectionObject) {
        return formatString.FormatWith(GetPropertiesDictionary(injectionObject));
    }

    /// <summary>
    /// Extension method that replaces keys in a string with the values of matching dictionary entries.
    /// </summary>
    /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
    /// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
    /// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
    public static string FormatWith(this string formatString, IDictionary<string, object> dictionary) {
        char openBraceChar = '{';
        char closeBraceChar = '}';

        return FormatWith(formatString, dictionary, openBraceChar, closeBraceChar);
    }
        /// <summary>
        /// Extension method that replaces keys in a string with the values of matching dictionary entries.
        /// </summary>
        /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
        /// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
        /// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
    public static string FormatWith(this string formatString, IDictionary<string, object> dictionary, char openBraceChar, char closeBraceChar) {
        string result = formatString;
        if (dictionary == null || formatString == null)
            return result;

        // start the state machine!

        // ballpark output string as two times the length of the input string for performance (avoids reallocating the buffer as often).
        StringBuilder outputString = new StringBuilder(formatString.Length * 2);
        StringBuilder currentKey = new StringBuilder();

        bool insideBraces = false;

        int index = 0;
        while (index < formatString.Length) {
            if (!insideBraces) {
                // currently not inside a pair of braces in the format string
                if (formatString[index] == openBraceChar) {
                    // check if the brace is escaped
                    if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
                        // add a brace to the output string
                        outputString.Append(openBraceChar);
                        // skip over braces
                        index += 2;
                        continue;
                    }
                    else {
                        // not an escaped brace, set state to inside brace
                        insideBraces = true;
                        index++;
                        continue;
                    }
                }
                else if (formatString[index] == closeBraceChar) {
                    // handle case where closing brace is encountered outside braces
                    if (index < formatString.Length - 1 && formatString[index + 1] == closeBraceChar) {
                        // this is an escaped closing brace, this is okay
                        // add a closing brace to the output string
                        outputString.Append(closeBraceChar);
                        // skip over braces
                        index += 2;
                        continue;
                    }
                    else {
                        // this is an unescaped closing brace outside of braces.
                        // throw a format exception
                        throw new FormatException($"Unmatched closing brace at position {index}");
                    }
                }
                else {
                    // the character has no special meaning, add it to the output string
                    outputString.Append(formatString[index]);
                    // move onto next character
                    index++;
                    continue;
                }
            }
            else {
                // currently inside a pair of braces in the format string
                // found an opening brace
                if (formatString[index] == openBraceChar) {
                    // check if the brace is escaped
                    if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
                        // there are escaped braces within the key
                        // this is illegal, throw a format exception
                        throw new FormatException($"Illegal escaped opening braces within a parameter - index: {index}");
                    }
                    else {
                        // not an escaped brace, we have an unexpected opening brace within a pair of braces
                        throw new FormatException($"Unexpected opening brace inside a parameter - index: {index}");
                    }
                }
                else if (formatString[index] == closeBraceChar) {
                    // handle case where closing brace is encountered inside braces
                    // don't attempt to check for escaped braces here - always assume the first brace closes the braces
                    // since we cannot have escaped braces within parameters.

                    // set the state to be outside of any braces
                    insideBraces = false;

                    // jump over brace
                    index++;

                    // at this stage, a key is stored in current key that represents the text between the two braces
                    // do a lookup on this key
                    string key = currentKey.ToString();
                    // clear the stringbuilder for the key
                    currentKey.Clear();

                    object outObject;

                    if (!dictionary.TryGetValue(key, out outObject)) {
                        // the key was not found as a possible replacement, throw exception
                        throw new FormatException($"The parameter "{key}" was not present in the lookup dictionary");
                    }

                    // we now have the replacement value, add the value to the output string
                    outputString.Append(outObject);

                    // jump to next state
                    continue;
                } // if }
                else {
                    // character has no special meaning, add it to the current key
                    currentKey.Append(formatString[index]);
                    // move onto next character
                    index++;
                    continue;
                } // else
            } // if inside brace
        } // while

        // after the loop, if all braces were balanced, we should be outside all braces
        // if we're not, the input string was misformatted.
        if (insideBraces) {
            throw new FormatException("The format string ended before the parameter was closed.");
        }

        return outputString.ToString();
    }

    /// <summary>
    /// Creates a Dictionary from an objects properties, with the Key being the property's
    /// name and the Value being the properties value (of type object)
    /// </summary>
    /// <param name="properties">An object who's properties will be used</param>
    /// <returns>A <see cref="Dictionary"/> of property values </returns>
    private static Dictionary<string, object> GetPropertiesDictionary(object properties) {
        Dictionary<string, object> values = null;
        if (properties != null) {
            values = new Dictionary<string, object>();
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(properties);
            foreach (PropertyDescriptor prop in props) {
                values.Add(prop.Name, prop.GetValue(properties));
            }
        }
        return values;
    }
}

最终,所有的逻辑归结为10种主要状态——因为当状态机在一个括号外,同样在一个括号内时,下一个字符要么是左大括号,要么是转义左大括号,要么是右大括号,要么是转义右大括号,要么是普通字符。随着循环的进行,这些条件中的每一个都被单独处理,向输出StringBuffer或键StringBuffer添加字符。当参数关闭时,键StringBuffer的值用于在字典中查找参数的值,然后将其推送到输出StringBuffer中。最后,返回输出StringBuffer的值。


我实现了这个简单的类,它复制了string.format的功能(使用类时除外)。您可以使用字典或类型来定义字段。

https://github.com/sergueifederov/namedformatstring

C 6.0正在将此功能直接添加到语言规范中,因此NamedFormatString是为了向后兼容。


我用与现有解决方案稍有不同的方式解决了这个问题。它完成命名项替换的核心(而不是一些已经完成的反射位)。它非常快速和简单…这是我的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/// <summary>
/// Formats a string with named format items given a template dictionary of the items values to use.
/// </summary>
public class StringTemplateFormatter
{
    private readonly IFormatProvider _formatProvider;

    /// <summary>
    /// Constructs the formatter with the specified <see cref="IFormatProvider"/>.
    /// This is defaulted to <see cref="CultureInfo.CurrentCulture">CultureInfo.CurrentCulture</see> if none is provided.
    /// </summary>
    /// <param name="formatProvider"></param>
    public StringTemplateFormatter(IFormatProvider formatProvider = null)
    {
        _formatProvider = formatProvider ?? CultureInfo.CurrentCulture;
    }

    /// <summary>
    /// Formats a string with named format items given a template dictionary of the items values to use.
    /// </summary>
    /// <param name="text">The text template</param>
    /// <param name="templateValues">The named values to use as replacements in the formatted string.</param>
    /// <returns>The resultant text string with the template values replaced.</returns>
    public string FormatTemplate(string text, Dictionary<string, object> templateValues)
    {
        var formattableString = text;
        var values = new List<object>();
        foreach (KeyValuePair<string, object> value in templateValues)
        {
            var index = values.Count;
            formattableString = ReplaceFormattableItem(formattableString, value.Key, index);
            values.Add(value.Value);
        }
        return String.Format(_formatProvider, formattableString, values.ToArray());
    }

    /// <summary>
    /// Convert named string template item to numbered string template item that can be accepted by <see cref="string.Format(string,object[])">String.Format</see>
    /// </summary>
    /// <param name="formattableString">The string containing the named format item</param>
    /// <param name="itemName">The name of the format item</param>
    /// <param name="index">The index to use for the item value</param>
    /// <returns>The formattable string with the named item substituted with the numbered format item.</returns>
    private static string ReplaceFormattableItem(string formattableString, string itemName, int index)
    {
        return formattableString
            .Replace("{" + itemName +"}","{" + index +"}")
            .Replace("{" + itemName +",","{" + index +",")
            .Replace("{" + itemName +":","{" + index +":");
    }
}

其使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
    [Test]
    public void FormatTemplate_GivenANamedGuid_FormattedWithB_ShouldFormatCorrectly()
    {
        // Arrange
        var template ="My guid {MyGuid:B} is awesome!";
        var templateValues = new Dictionary<string, object> { {"MyGuid", new Guid("{A4D2A7F1-421C-4A1D-9CB2-9C2E70B05E19}") } };
        var sut = new StringTemplateFormatter();
        // Act
        var result = sut.FormatTemplate(template, templateValues);
        //Assert
        Assert.That(result, Is.EqualTo("My guid {a4d2a7f1-421c-4a1d-9cb2-9c2e70b05e19} is awesome!"));
    }

希望有人发现这有用!


1
2
3
string language ="Python";
int numquotes = 2;
string output = language +" has"+ numquotes +" language types.";

编辑:我应该说的是,"不,我不相信你想做的是得到C的支持。这是你能得到的最接近的。"