在C#中命名的string格式
有没有什么办法来格式化string的名称,而不是在C#中的位置?
在Python中,我可以做这样的事情(从这里无耻地被盗):
>>> print '%(language)s has %(#)03d quote types.' % \ {'language': "Python", "#": 2} Python has 002 quote types.
有没有办法在C#中做到这一点? 比如说:
String.Format("{some_variable}: {some_other_variable}", ...);
能够使用variables名称来做到这一点很好,但字典也是可以接受的。
没有内置的方法来处理这个。
这里有一个方法
string myString = "{foo} is {bar} and {yadi} is {yada}".Inject(o);
这是另一个
Status.Text = "{UserName} last logged in at {LastLoginDate}".FormatWith(user);
菲尔·哈克(Phil Haack) 在第二部分的基础上改进了第三种方法
我有一个实现,我刚刚发布到我的博客在这里: http : //haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx
它解决了这些其他实现带有大括号转义的一些问题。 这个职位有细节。 它也是DataBinder.Eval的东西,但仍然非常快。
你也可以使用这样的匿名types:
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; }
当然,如果你还想分析格式,就需要更多的代码,但是你可以使用这个函数来格式化一个string,如:
Format("test {first} and {another}", new { first = "something", another = "something else" })
内插string被添加到C#6.0和Visual Basic 14中
两者都是在Visual Studio 2015中通过新的Roslyn编译器引入的。
-
C#6.0:
return "\{someVariable} and also \{someOtherVariable}"
或者
return $"{someVariable} and also {someOtherVariable}"
-
来源: C#6.0中有什么新东西
-
-
VB 14:
return $"{someVariable} and also {someOtherVariable}"
- 来源: 什么是新的VB 14
值得注意的function(在Visual Studio 2015 IDE中):
- 支持语法着色 – 高亮显示string中包含的variables
- 支持重构 – 重命名时,包含在string中的variables也被重命名
- 实际上不仅是variables名称,而且支持expression式 ,例如不仅是
{index}
起作用,还有{(index + 1).ToString().Trim()}
请享用! (&在VS中点击“发送微笑”)
似乎没有办法做到这一点。 虽然,看起来可行的实现自己的IFormatProvider
链接到一个IDictionary
的值。
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);
输出:
Python有2种报价types
需要注意的是,你不能混合使用FormatProviders
,所以花哨的文本格式不能同时使用。
框架本身并没有提供这样做的方法,但你可以看看Scott Hanselman的这篇文章 。 用法示例:
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);
James Newton-King的这个代码与子属性和索引类似,
string foo = "Top result for {Name} was {Results[0].Name}".FormatWith(student));
James的代码依赖于System.Web.UI.DataBinder来parsingstring,并需要引用System.Web,有些人不喜欢在非Web应用程序中使用。
编辑:哦,他们和匿名types很好地工作,如果你没有一个对象的属性准备好了:
string name = ...; DateTime date = ...; string foo = "{Name} - {Birthday}".FormatWith(new { Name = name, Birthday = date });
请参阅https://stackoverflow.com/questions/271398?page=2#358259
通过链接扩展你可以这样写:
var str = "{foo} {bar} {baz}".Format(foo=>"foo", bar=>2, baz=>new object());
你会得到"foo 2 System.Object
”。
我认为最接近你会得到一个索引格式:
String.Format("{0} has {1} quote types.", "C#", "1");
还有String.Replace(),如果你愿意在多个步骤中做到这一点,并坚信你不会在string中的其他任何地方find你的“variables”:
string MyString = "{language} has {n} quote types."; MyString = MyString.Replace("{language}", "C#").Replace("{n}", "1");
扩展这个使用列表:
List<KeyValuePair<string, string>> replacements = GetFormatDictionary(); foreach (KeyValuePair<string, string> item in replacements) { MyString = MyString.Replace(item.Key, item.Value); }
你可以通过迭代它的.Keys集合,但是通过使用一个List <KeyValuePair <string,string>,我们可以利用List的.ForEach()方法来利用Dictionary <string,string>单行:
replacements.ForEach(delegate(KeyValuePair<string,string>) item) { MyString = MyString.Replace(item.Key, item.Value);});
拉姆达会更简单,但我仍然在.Net 2.0上。 另外请注意,.Replace()的性能在迭代使用时并不稳定,因为.Net中的string是不可变的。 另外,这要求MyString
variables被定义为可以被MyString
访问,所以它还不完美。
我的开源库Regextra支持命名格式(除其他外)。 它目前的目标是.NET 4.0+,并可在NuGet上使用 。 我也有一个关于它的介绍性博客文章: Regextra:帮助你减less你的(问题){2} 。
命名的格式化位支持:
- 基本格式
- 嵌套的属性格式
- 字典格式
- 转义分隔符
- 标准/自定义/ IFormatProviderstring格式
例:
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);
结果:
我们刚刚发出您的订单的“小工具”,放置在2/28/2014。 您的{credit}卡将收取$ 1,500.00。
查看项目的GitHub链接(上图)和wiki以获取其他示例。
检查这一个:
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()); }); }
样品:
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));
与其他解决scheme相比,性能相当不错。
我怀疑这是可能的。 首先想到的是你将如何访问局部variables名?
但是,使用LINQ和Lambdaexpression式可能会有一些巧妙的方法。
这是我回来的一个。 它使用Format方法扩展String,并带有一个参数。 好的是它会使用标准的string.Format,如果你提供一个像int这样简单的参数,但是如果你使用类似匿名types的东西,它也可以工作。
用法示例:
"The {Name} family has {Children} children".Format(new { Children = 4, Name = "Smith" })
会导致“史密斯家有4个孩子”。
它不会像数组和索引器那样疯狂绑定。 但它是超级简单和高性能。
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; } } }
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); }
例:
var builder = new StringBuilder(); builder.AppendNamedFormat( @"你好,{Name},今天是{Date:yyyy/MM/dd}, 这是你第{LoginTimes}次login,积分{Score:{{ 0.00 }}}", new Dictionary<string, object>() { { "Name", "wayjet" }, { "LoginTimes",18 }, { "Score", 100.4 }, { "Date",DateTime.Now } });
输出:你好,wayjet,今天是2011-05-04,这是你第18次login,积分{100.40}
这里是任何对象的简单方法:
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; }
在这里如何使用它:
DateTime date = DateTime.Now; string dateString = StringWithFormat("{Month}/{Day}/{Year}", date);
输出:2/27/2012
我实现了这是一个简单的类,它重复String.Format的function(除了使用类时)。 您可以使用字典或types来定义字段。
https://github.com/SergueiFedorov/NamedFormatString
C#6.0正在将这个function添加到语言规范中,所以NamedFormatString
是为了向后兼容。
我用与现有解决scheme略有不同的方式解决了这个问题。 它做了指定项目replace的核心(不是某些已经完成的reflection位)。 这是非常快速和简单的…这是我的解决scheme:
/// <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 + ":"); } }
它以如下方式使用:
[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!")); }
希望有人认为这有用!
尽pipe被接受的答案给出了一些很好的例子,但是这个例子还有一些Haack例子没有处理转义。 许多还严重依赖于正则expression式(较慢)或DataBinder.Eval,这在.NET Core上不可用,在其他一些环境中也是如此。
考虑到这一点,我写了一个简单的基于状态机的parsing器,通过字符stream,写入一个StringBuilder
输出。 它以String
扩展方法的forms实现,可以使用Dictionary<string, object>
或带有参数的object
作为input(使用reflection)。
它处理{{{escaping}}}
无限级别,并在input包含不平衡大括号和/或其他错误时引发FormatException
。
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
。 当一个参数被closures时,关键字StringBuffer
的值被用来查找字典中的参数值,然后被压入到输出StringBuffer
。 最后,返回输出StringBuffer
的值。
string language = "Python"; int numquotes = 2; string output = language + " has "+ numquotes + " language types.";
编辑:我应该说的是,“不,我不相信你想要做的事情是由C#支持的,这和你将要得到的一样接近。