How to create more convenient string.format syntax?

I need to create a very long string in a program and use String.Format. The problem I am facing is tracking all numbers when you have more than 8-10 parameters.

Is it possible to create some form of overload that takes a syntax like this?

String.Format("You are {age} years old and your last name is {name} ", {age = "18", name = "Foo"}); 
+44
string c #
Aug 24 '09 at 12:21
source share
6 answers

How about the following, which works for both anonymous types (example below) and ordinary 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"}); } 

via:

 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<string, string>(); 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; }); } 
+70
Aug 24 '09 at 12:33
source share

not exactly the same, but sort of spoofing it ... use an extension method, a dictionary, and a little code:

something like that...

  public static class Extensions { public static string FormatX(this string format, params KeyValuePair<string, object> [] values) { string res = format; foreach (KeyValuePair<string, object> kvp in values) { res = res.Replace(string.Format("{0}", kvp.Key), kvp.Value.ToString()); } return res; } } 
+2
Aug 24 '09 at 12:25
source share

primitive implementation:

 public static class StringUtility { public static string Format(string pattern, IDictionary<string, object> args) { StringBuilder builder = new StringBuilder(pattern); foreach (var arg in args) { builder.Replace("{" + arg.Key + "}", arg.Value.ToString()); } return builder.ToString(); } } 

Using:

 StringUtility.Format("You are {age} years old and your last name is {name} ", new Dictionary<string, object>() {{"age" = 18, "name" = "Foo"}}); 

You can also use an anonymous class, but this is much slower due to the reflection you need.

For a real implementation, you should use regex for

  • avoid {}
  • check if there are placeholders that are not replaced, which is most likely a programming error.
+1
Aug 24 '09 at 12:33
source share

How about whether age / name will be a variable in your application. So you need view syntax to make it almost unique, like {age_1}?

If you have problems with 8-10 parameters: why not use

 "You are " + age + " years old and your last name is " + name + " 
+1
Aug 24 '09 at 12:34
source share

Starting in C # 6, this type of string interpolation can now be used with the new string> syntax

 var formatted = $"You are {age} years old and your last name is {name}"; 
+1
Oct 07 '15 at 11:31
source share

Although C # 6.0 can now do this with string interpolation, sometimes it needs to be done with dynamic format strings at runtime. I could not use other methods that require DataBinder.Eval due to the fact that they are not available in .NET Core, and were dissatisfied with the work of Regex solutions.

With that in mind, here I wrote a regular expression based analyzer. It handles unlimited levels {{{escaping}}} and throws a FormatException when an input contains unbalanced curly braces and / or other errors. Although the main method accepts a Dictionary<string, object> , the helper method can also take an object and use its parameters through reflection.

 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 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; } } 

Ultimately, the whole logic comes down to 10 basic states: if the state machine is outside the bracket, and also inside the bracket, the next character is either an open curly bracket, an open bracket, a closed bracket, a closed bracket, or a regular character. Each of these conditions is processed individually during the cycle, adding characters to either the output value of the StringBuffer or the StringBuffer key. When the parameter is closed, the StringBuffer key value is used to find the parameter value in the dictionary, which is then inserted into the StringBuffer output.

EDIT:

I turned this into a complete project at https://github.com/crozone/FormatWith

0
Feb 23 '16 at 2:40
source share



All Articles