2009-08-24 3 views
44

Ich muss eine sehr lange Zeichenfolge in einem Programm erstellen und String.Format verwendet haben. Das Problem, dem ich gegenüberstehe, ist es, alle Zahlen zu verfolgen, wenn Sie mehr als 8-10 Parameter haben.Wie kann ich eine benutzerfreundlichere string.format-Syntax erstellen?

Ist es möglich, eine Form der Überladung zu erstellen, die eine ähnliche Syntax akzeptiert?

String.Format("You are {age} years old and your last name is {name} ", 
{age = "18", name = "Foo"}); 
+0

Jeder upvote gedreht habe wurde von einem Lieblings gefolgt. –

Antwort

70

Wie über die folgenden, die sowohl für anonyme Typen (das Beispiel unten) oder regelmäßige Typen (Domain Einheiten, etc.) funktioniert:

static void Main() 
{ 
    string s = Format("You are {age} years old and your last name is {name} ", 
     new {age = 18, name = "Foo"}); 
} 

mit:

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; 
    }); 
} 
+0

ausgezeichnet - Ich mag es! –

+0

Ich konnte jetzt nicht anonyme Typen wie diese verwenden. Es ist nicht nur. Net 4 ist es? –

+4

Plus wird es für Domain-Entities funktionieren, zB 'Format (" Dear {Title} {Vorname}, ... ", Person)' –

2

nicht ganz das gleiche, aber es Art von Spoofing ... eine Erweiterungsmethode verwenden, ein Wörterbuch und ein wenig Code:

so etwas wie dieses ...

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

    } 
+1

Erweiterungsmethode funktioniert nicht, String.Format ist statisch. Aber Sie können einfach eine neue statische Methode erstellen. –

+0

sicher hast du recht! Nur noch ein Top von meinem Kopf gehen ... –

1

primitive Implementierung :

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

Verbrauch:

Sie könnten auch eine anonyme Klasse verwenden, aber das ist viel langsamer wegen der Reflexion, die Sie benötigen.

Für eine echte Implementierung sollten Sie regulären Ausdruck

  • verwenden lassen entkommen die {}
  • überprüfen, ob es gibt Platzhalter, dass, wenn nicht ersetzt, was die meisten ist wahrscheinlich ein Programmierfehler.
1

Was Etwa wenn Alter/Name eine Variable in Ihrer Anwendung ist. Sie würden also eine Sortier-Syntax benötigen, um sie fast einzigartig zu machen wie {age_1}?

Wenn Sie Probleme mit 8-10 Parameter haben: warum nicht verwenden

"You are " + age + " years old and your last name is " + name + " 
+0

+1 für die Einfachheit und Bereitschaft, die Zeichenfolge Bucket.Format Anforderung in der ursprünglichen Frage. Obwohl ich die Lösung von Marc Gravell mag. –

+0

In meinem einfachen Beispiel könnten Sie, aber wenn Sie sagen, HTML, wird es noch schwieriger zu lesen. string test = ""; – Espo

+0

so wahr, es hängt wirklich von der Verwendung ab. Auch String.Format mit html ist beschissen zu lesen – RvdK

1

Ab C# 6, diese Art von String-Interpolation ist nun möglich, die neue string interpolation Syntax:

var formatted = $"You are {age} years old and your last name is {name}"; 
0

Obwohl C# 6.0 dies jetzt mit der Zeichenfolgeninterpolation ausführen kann, ist dies manchmal zur Laufzeit mit dynamischen Formatzeichenfolgen erforderlich. Ich konnte keine anderen Methoden verwenden, die DataBinder.Eval erfordern, da sie in .NET Core nicht verfügbar sind und mit der Leistung von Regex-Lösungen nicht zufrieden sind.

In diesem Sinne, hier ist eine Regex-freie, State Machine Parser, die ich geschrieben habe. Es behandelt unbegrenzte Ebenen von {{{escaping}}} und wirft FormatException, wenn die Eingabe unsymmetrische Klammern und/oder andere Fehler enthält. Obwohl die Hauptmethode eine Dictionary<string, object> dauert, kann die Hilfsmethode auch eine object nehmen und ihre Parameter über Reflektion verwenden.

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

Letztlich alle die Logik nach unten in 10 Hauptzustände kocht - Denn wenn die Zustandsmaschine außerhalb einer Konsole ist und ebenfalls innerhalb einer Klammer, das nächste Zeichen entweder eine offene Klammer, eine entflohener offene Klammer, ein geschlossen Klammer, eine entkommene geschlossene Klammer oder ein gewöhnlicher Charakter. Jede dieser Bedingungen wird einzeln behandelt, während die Schleife fortschreitet, wobei Zeichen entweder zu einem Ausgang StringBuffer oder einem Schlüssel StringBuffer hinzugefügt werden. Wenn ein Parameter geschlossen ist, wird der Wert des Schlüssels StringBuffer verwendet, um den Wert des Parameters im Wörterbuch nachzuschlagen, der dann in den Ausgang StringBuffer geschoben wird.

EDIT:

ich dies in eine voll auf Projekt https://github.com/crozone/FormatWith

Verwandte Themen