2009-08-24 13 views
44

Potrzebuję utworzyć bardzo długi ciąg w programie i używam String.Format. Problem, przed którym stoję, polega na śledzeniu wszystkich liczb, gdy masz więcej niż 8-10 parametrów.Jak mogę utworzyć bardziej przyjazną dla użytkownika składnię string.format?

Czy można stworzyć jakąś formę przeciążenia, która będzie akceptować składnię podobną do tej?

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

Każdy upvote nastąpiła ulubionych. –

Odpowiedz

70

Jak o następujących, który działa zarówno dla anonimowych typów (poniżej przykład), lub zwykłych typów (podmioty domeny, etc):

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

Zastosowanie:

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

znakomity - podoba mi się! –

+0

Nie mogłem teraz używać takich anonimowych typów. To nie tylko .net 4 jest? –

+4

Plus będzie działać dla podmiotów domeny, tj. "Format (" Drodzy {Tytuł} {Imię}, ... ", osoba)' –

2

nie całkiem to samo, ale rodzaj fałszowanie go ... wykorzystać metodę rozszerzenia, słownika i trochę kodu:

coś takiego ...

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

Metoda rozszerzenia nie działa, String.Format jest statyczny. Ale możesz po prostu stworzyć nową metodę statyczną. –

+0

masz rację! Kolejny szczyt mojej głowy ... –

1

prymitywny realizacji :

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

Zastosowanie:

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

Można również użyć klasy anonimowej, ale jest to znacznie wolniejsze ze względu na odbicie, które będzie potrzebne.

Dla prawdziwego realizacji należy użyć wyrażenia regularnego do

  • umożliwienia ucieczki {}
  • sprawdź czy są zastępcze, że gdzie nie zastąpiły, który jest najprawdopodobniej błąd programowania.
1

Co o tym, czy wiek/nazwa jest zmienną w twojej aplikacji. Więc potrzebujesz składni sortowania, aby była prawie wyjątkowa jak {age_1}?

Jeśli masz kłopoty z 8-10 parametrów: dlaczego nie korzystają

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

+1 dla prostoty i chęci spasowania łańcucha. Wymagane sformatowanie w pierwotnym pytaniu. Chociaż podoba mi się rozwiązanie Marca Gravella. –

+0

W moim prostym przykładzie, mogłeś, ale kiedy wypisujesz, powiedz HTML, to jest jeszcze trudniejsze do odczytania. ciąg znaków = ""; – Espo

+0

prawda, to naprawdę zależy od użytkowania. Nawet String.Format z html jest brzydki, aby przeczytać – RvdK

1

As z C# 6, ten rodzaj interpolacji smyczkowy jest obecnie możliwe przy użyciu nowego string interpolation składnię:

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

Mimo że C# 6.0 może teraz robić to z interpolacją ciągów, czasami jest to konieczne z dynamicznymi ciągami formatów w czasie wykonywania. Nie mogłem użyć innych metod, które wymagają DataBinder.Eval, ponieważ nie są one dostępne w .NET Core i były niezadowolone z wydajności rozwiązań Regex.

Mając to na uwadze, oto darmowy, oparty na języku Regex parser, który napisałem. Obsługuje nieograniczoną liczbę poziomów {{{escaping}}} i rzuca FormatException, gdy wejście zawiera niesymetryczne nawiasy klamrowe i/lub inne błędy. Chociaż główna metoda przyjmuje postać Dictionary<string, object>, metoda pomocnicza może również przyjmować wartość object i używać jej parametrów za pomocą odbicia.

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

Ostatecznie cała logika sprowadza się do 10 głównych państw - W przypadku, gdy maszyna stan jest poza wspornikiem a także wewnątrz wspornika, następny znak jest albo otwarty nawias, zbiegłym otwarta klamra, zamkniętego klamra, klamra zamknięta z ucieczką lub zwykła postać. Każdy z tych warunków jest obsługiwany indywidualnie w miarę postępu pętli, dodając znaki do wyjścia StringBuffer lub klucza StringBuffer. Gdy parametr jest zamknięty, wartość klucza StringBuffer jest używana do sprawdzenia wartości parametru w słowniku, która następnie zostaje przekazana do wyjścia StringBuffer.

EDIT:

Zrobiłem to pod pełną nad projektem w https://github.com/crozone/FormatWith

Powiązane problemy