可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I need to create a very long string in a program, and have been using String.Format. The problem I am facing is keeping track of all the numbers when you have more than 8-10 parameters.
Is it possible to create some form of overload that will accept a syntax similar to this?
String.Format("You are {age} years old and your last name is {name} ", {age = "18", name = "Foo"});
回答1:
How about the following, which works both for anonymous types (the example below), or regular types (domain entities, etc):
static void Main() { string s = Format("You are {age} years old and your last name is {name} ", new {age = 18, name = "Foo"}); }
using:
static readonly Regex rePattern = new Regex( @"(\{+)([^\}]+)(\}+)", RegexOptions.Compiled); static string Format(string pattern, object template) { if (template == null) throw new ArgumentNullException(); Type type = template.GetType(); var cache = new Dictionary(); return rePattern.Replace(pattern, match => { int lCount = match.Groups[1].Value.Length, rCount = match.Groups[3].Value.Length; if ((lCount % 2) != (rCount % 2)) throw new InvalidOperationException("Unbalanced braces"); string lBrace = lCount == 1 ? "" : new string('{', lCount / 2), rBrace = rCount == 1 ? "" : new string('}', rCount / 2); string key = match.Groups[2].Value, value; if(lCount % 2 == 0) { value = key; } else { if (!cache.TryGetValue(key, out value)) { var prop = type.GetProperty(key); if (prop == null) { throw new ArgumentException("Not found: " + key, "pattern"); } value = Convert.ToString(prop.GetValue(template, null)); cache.Add(key, value); } } return lBrace + value + rBrace; }); }
回答2:
not quite the same but sort of spoofing it... use an extension method, a dictionary and a little code:
something like this...
public static class Extensions { public static string FormatX(this string format, params KeyValuePair [] values) { string res = format; foreach (KeyValuePair kvp in values) { res = res.Replace(string.Format("{0}", kvp.Key), kvp.Value.ToString()); } return res; } }
回答3:
What about if age/name is an variable in your application. So you would need a sort syntax to make it almost unique like {age_1}?
If you have trouble with 8-10 parameters: why don't use
"You are " + age + " years old and your last name is " + name + "
回答4:
primitive implementation:
public static class StringUtility { public static string Format(string pattern, IDictionary args) { StringBuilder builder = new StringBuilder(pattern); foreach (var arg in args) { builder.Replace("{" + arg.Key + "}", arg.Value.ToString()); } return builder.ToString(); } }
Usage:
StringUtility.Format("You are {age} years old and your last name is {name} ", new Dictionary() {{"age" = 18, "name" = "Foo"}});
You could also use a anonymous class, but this is much slower because of the reflection you'll need.
For a real implementation you should use regular expression to
- allow escaping the {}
- check if there are placeholders that where not replaced, which is most probably a programming error.
回答5:
As of C#6, this kind of string interpolation is now possible using the new string interpolation syntax:
var formatted = $"You are {age} years old and your last name is {name}";
回答6:
Although C# 6.0 can now do this with string interpolation, it's sometimes necessary to do this with dynamic format strings at runtime. I've been unable to use other methods that require DataBinder.Eval due to them not being available in .NET Core, and have been dissatisfied with the performance of Regex solutions.
With that in mind, here's a Regex free, state machine based parser that I've written up. It handles unlimited levels of {{{escaping}}}
and throws FormatException
when input contains unbalanced braces and/or other errors. Although the main method takes a Dictionary
, the helper method can also take an object
and use its parameters via reflection.
public static class StringExtension { /// /// Extension method that replaces keys in a string with the values of matching object properties. /// /// The format string, containing keys like {foo} and {foo:SomeFormat}. /// The object whose properties should be injected in the string /// A version of the formatString string with keys replaced by (formatted) key values. public static string FormatWith(this string formatString, object injectionObject) { return formatString.FormatWith(GetPropertiesDictionary(injectionObject)); } /// /// Extension method that replaces keys in a string with the values of matching dictionary entries. /// /// The format string, containing keys like {foo} and {foo:SomeFormat}. /// An with keys and values to inject into the string /// A version of the formatString string with dictionary keys replaced by (formatted) key values. public static string FormatWith(this string formatString, IDictionary dictionary) { char openBraceChar = '{'; char closeBraceChar = '}'; return FormatWith(formatString, dictionary, openBraceChar, closeBraceChar); } /// /// Extension method that replaces keys in a string with the values of matching dictionary entries. /// /// The format string, containing keys like {foo} and {foo:SomeFormat}. /// An with keys and values to inject into the string /// A version of the formatString string with dictionary keys replaced by (formatted) key values. public static string FormatWith(this string formatString, IDictionary 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 /// 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) /// /// An object who's properties will be used /// A of property values private static Dictionary GetPropertiesDictionary(object properties) { Dictionary values = null; if (properties != null) { values = new Dictionary(); PropertyDescriptorCollection props = TypeDescriptor.GetProperties(properties); foreach (PropertyDescriptor prop in props) { values.Add(prop.Name, prop.GetValue(properties)); } } return values; } }
Ultimately, all the logic boils down into 10 main states - For when the state machine is outside a bracket and likewise inside a bracket, the next character is either an open brace, an escaped open brace, a closed brace, an escaped closed brace, or an ordinary character. Each of these conditions is handled individually as the loop progresses, adding characters to either an output StringBuffer
or a key StringBuffer
. When a parameter is closed, the value of the key StringBuffer
is used to look up the parameter's value in the dictionary, which then gets pushed into the output StringBuffer
.
EDIT:
I've turned this into a full on project at https://github.com/crozone/FormatWith