Formattazione della stringa denominata in C #

Esiste un modo per formattare una stringa in base al nome anziché in C #?

In Python, posso fare qualcosa come questo esempio (rubato spudoratamente da qui ):

>>> print '%(language)s has %(#)03d quote types.' % \ {'language': "Python", "#": 2} Python has 002 quote types. 

C’è un modo per farlo in C #? Dì ad esempio:

 String.Format("{some_variable}: {some_other_variable}", ...); 

Essere in grado di farlo usando un nome di variabile sarebbe bello, ma anche un dizionario è accettabile.

    Non esiste un metodo integrato per gestirlo.

    Ecco un metodo

     string myString = "{foo} is {bar} and {yadi} is {yada}".Inject(o); 

    Eccone un altro

     Status.Text = "{UserName} last logged in at {LastLoginDate}".FormatWith(user); 

    Un terzo metodo migliorato parzialmente basato sui due precedenti , di Phil Haack

    Ho una implementazione che ho appena postato sul mio blog qui: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx

    Affronta alcuni problemi che queste altre implementazioni hanno con l’escape del brace. Il post ha dettagli. Fa anche la cosa DataBinder.Eval, ma è ancora molto veloce.

    Puoi anche usare tipi anonimi come questo:

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

    Ovviamente richiederebbe più codice se si desidera anche analizzare la formattazione, ma è ansible formattare una stringa usando questa funzione come:

     Format("test {first} and {another}", new { first = "something", another = "something else" }) 

    Le stringhe interpolate sono state aggiunte in C # 6.0 e Visual Basic 14

    Entrambi sono stati introdotti attraverso il nuovo compilatore Roslyn in Visual Studio 2015 .

    • C # 6.0:

      return "\{someVariable} and also \{someOtherVariable}" OR
      return $"{someVariable} and also {someOtherVariable}"

      • fonte: novità in C # 6.0

    • VB 14:

      return $"{someVariable} and also {someOtherVariable}"

      • fonte: novità in VB 14

    Funzionalità degne di nota (in IDE di Visual Studio 2015):

    • la colorazione della syntax è supportata – le variabili contenute nelle stringhe sono evidenziate
    • il refactoring è supportato – quando si rinomina, anche le variabili contenute nelle stringhe vengono ridenominate
    • in realtà non sono solo i nomi delle variabili, ma le espressioni sono supportate – per esempio non solo {index} funziona, ma anche {(index + 1).ToString().Trim()}

    Godere! (e fai clic su “Invia un sorriso” nel VS)

    Non sembra essere un modo per farlo fuori dagli schemi. Tuttavia, sembra fattibile implementare il proprio IFormatProvider che si collega a un IDictionary per i valori.

     var Stuff = new Dictionary { { "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); 

    Uscite:

      Python ha 2 tipi di quote 

    L’avvertenza è che non è ansible combinare FormatProviders , quindi la formattazione del testo elaborato non può essere utilizzata allo stesso tempo.

    Lo stesso framework non fornisce un modo per farlo, ma puoi dare un’occhiata a questo post di Scott Hanselman. Esempio di utilizzo:

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

    Questo codice di James Newton-King è simile e funziona con sotto-proprietà e indici,

     string foo = "Top result for {Name} was {Results[0].Name}".FormatWith(student)); 

    Il codice di James si basa su System.Web.UI.DataBinder per analizzare la stringa e richiede il riferimento System.Web, che alcune persone non amano fare in applicazioni non web.

    EDIT: Oh e funzionano bene con i tipi anonimi, se non hai un object con proprietà pronte per questo:

     string name = ...; DateTime date = ...; string foo = "{Name} - {Birthday}".FormatWith(new { Name = name, Birthday = date }); 

    Vedi https://stackoverflow.com/questions/271398?page=2#358259

    Con l’estensione linkata puoi scrivere questo:

     var str = "{foo} {bar} {baz}".Format(foo=>"foo", bar=>2, baz=>new object()); 

    e otterrai "foo 2 System.Object “.

    Penso che il più vicino che otterrai sia un formato indicizzato:

     String.Format("{0} has {1} quote types.", "C#", "1"); 

    C’è anche String.Replace (), se sei disposto a farlo in più passaggi e fai affidamento sul fatto che non troverai le tue ‘variabili’ in nessun altro punto della stringa:

     string MyString = "{language} has {n} quote types."; MyString = MyString.Replace("{language}", "C#").Replace("{n}", "1"); 

    Espandendo questo per usare una lista:

     List> replacements = GetFormatDictionary(); foreach (KeyValuePair item in replacements) { MyString = MyString.Replace(item.Key, item.Value); } 

    Puoi farlo con un dizionario anche iterando le sue raccolte .Keys, ma usando un List > possiamo sfruttare il metodo .ForEach () della lista e ricondurlo a una nave singola:

     replacements.ForEach(delegate(KeyValuePair) item) { MyString = MyString.Replace(item.Key, item.Value);}); 

    Un lambda sarebbe ancora più semplice, ma sono ancora su .Net 2.0. Si noti inoltre che le prestazioni di .Replace () non sono stellari se utilizzate in modo iterativo, poiché le stringhe in .Net sono immutabili. Inoltre, questo richiede che la variabile MyString sia definita in modo tale che sia accessibile al delegato, quindi non è ancora perfetta.

    La mia libreria open source, Regextra , supporta la formattazione denominata (tra le altre cose). Attualmente è destinato a .NET 4.0+ ed è disponibile su NuGet . Ho anche un post di blog introduttivo al riguardo: Regextra: ti aiuta a ridurre i tuoi (problemi) {2} .

    Il bit di formattazione denominato supporta:

    • Formattazione di base
    • Formattazione delle proprietà nidificate
    • Dizionario di formattazione
    • Sfuggire ai delimitatori
    • Formattazione standard / personalizzata / stringa IFormatProvider

    Esempio:

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

    Risultato:

    Abbiamo appena spedito il tuo ordine di “Widget”, pubblicato il 28/02/2014. La tua carta di credito verrà accreditata $ 1.500,00.

    Controlla il link GitHub del progetto (sopra) e wiki per altri esempi.

    Controlla questo:

     public static string StringFormat(string format, object source) { var matches = Regex.Matches(format, @"\{(.+?)\}"); List 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()); }); } 

    Campione:

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

    Le prestazioni sono piuttosto ok rispetto ad altre soluzioni.

    Dubito che sarà ansible. La prima cosa che viene in mente è come hai intenzione di ottenere l’accesso ai nomi delle variabili locali?

    Tuttavia, potrebbe esserci un modo intelligente usando le espressioni LINQ e Lambda per farlo.

    Eccone uno che ho fatto un po ‘di tempo fa. Estende String con un metodo Format prendendo un singolo argomento. La cosa bella è che userà la stringa standard. Formatta se fornisci un argomento semplice come un int, ma se usi qualcosa come il tipo anonimo, funzionerà anche tu.

    Esempio di utilizzo:

     "The {Name} family has {Children} children".Format(new { Children = 4, Name = "Smith" }) 

    Il risultato sarebbe “La famiglia Smith ha 4 figli”.

    Non fa cose pazze vincolanti come array e indicizzatori. Ma è super semplice e ad alte prestazioni.

      public static class AdvancedFormatString { ///  /// 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. ///  ///  ///  ///  ///  /// "The {Name} family has {Children} children".Format(new { Children = 4, Name = "Smith" }) /// /// results in /// "This Smith family has 4 children ///  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 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 parsedStrings = new Dictionary(StringComparer.Ordinal); const char OpeningDelimiter = '{'; const char ClosingDelimiter = '}'; ///  /// Parses the given format string into a list of fragments. ///  ///  ///  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 fragments = new List(); while (true) { fragments.Add(currFrag); if (currFragEndIndex == lastCharIndex) { break; } currFrag = ParseFragment(format, currFragEndIndex + 1, out currFragEndIndex); } return fragments.ToArray(); } ///  /// Finds the next delimiter from the starting index. ///  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); } ///  /// Replaces escaped brackets, turning '{{' and '}}' into '{' and '}', respectively. ///  ///  ///  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; } ///  /// The literal value, or the name of the fragment, depending on fragment type. ///  public string Value { get; private set; } } } 
     private static Regex s_NamedFormatRegex = new Regex(@"\{(?!\{)(?[\w]+)(:(?(\{\{|\}\}|[^\{\}])*)?)?\}", RegexOptions.Compiled); public static StringBuilder AppendNamedFormat(this StringBuilder builder,IFormatProvider provider, string format, IDictionary 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 args) { if (builder == null) throw new ArgumentNullException("builder"); return builder.AppendNamedFormat(null, format, args); } 

    Esempio:

     var builder = new StringBuilder(); builder.AppendNamedFormat( @"你好,{Name},今天是{Date:yyyy/MM/dd}, 这是你第{LoginTimes}次登录,积分{Score:{{ 0.00 }}}", new Dictionary() { { "Name", "wayjet" }, { "LoginTimes",18 }, { "Score", 100.4 }, { "Date",DateTime.Now } }); 

    Uscita: 你好, wayjet, 今天 是 2011-05-04, 这 是 你 第 18 次 登录, 积分 {100.40}

    ecco un metodo semplice per qualsiasi object:

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

    E qui come usarlo:

      DateTime date = DateTime.Now; string dateString = StringWithFormat("{Month}/{Day}/{Year}", date); 

    produzione: 27/02/2012

    Ho implementato questa è una class semplice che duplica la funzionalità di String.Format (tranne quando si usano classi). È ansible utilizzare un dizionario o un tipo per definire i campi.

    https://github.com/SergueiFedorov/NamedFormatString

    C # 6.0 sta aggiungendo questa funzionalità direttamente nelle specifiche del linguaggio, quindi NamedFormatString è compatibile con le versioni precedenti.

    Ho risolto questo in un modo leggermente diverso rispetto alle soluzioni esistenti. Fa il nucleo della sostituzione dell’object nominato (non il bit di riflessione che alcuni hanno fatto). È estremamente veloce e semplice … Questa è la mia soluzione:

     ///  /// Formats a string with named format items given a template dictionary of the items values to use. ///  public class StringTemplateFormatter { private readonly IFormatProvider _formatProvider; ///  /// Constructs the formatter with the specified . /// This is defaulted to CultureInfo.CurrentCulture if none is provided. ///  ///  public StringTemplateFormatter(IFormatProvider formatProvider = null) { _formatProvider = formatProvider ?? CultureInfo.CurrentCulture; } ///  /// Formats a string with named format items given a template dictionary of the items values to use. ///  /// The text template /// The named values to use as replacements in the formatted string. /// The resultant text string with the template values replaced. public string FormatTemplate(string text, Dictionary templateValues) { var formattableString = text; var values = new List(); foreach (KeyValuePair value in templateValues) { var index = values.Count; formattableString = ReplaceFormattableItem(formattableString, value.Key, index); values.Add(value.Value); } return String.Format(_formatProvider, formattableString, values.ToArray()); } ///  /// Convert named string template item to numbered string template item that can be accepted by String.Format ///  /// The string containing the named format item /// The name of the format item /// The index to use for the item value /// The formattable string with the named item substituted with the numbered format item. private static string ReplaceFormattableItem(string formattableString, string itemName, int index) { return formattableString .Replace("{" + itemName + "}", "{" + index + "}") .Replace("{" + itemName + ",", "{" + index + ",") .Replace("{" + itemName + ":", "{" + index + ":"); } } 

    È usato nel modo seguente:

      [Test] public void FormatTemplate_GivenANamedGuid_FormattedWithB_ShouldFormatCorrectly() { // Arrange var template = "My guid {MyGuid:B} is awesome!"; var templateValues = new Dictionary { { "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!")); } 

    Spero che qualcuno lo ritenga utile!

    Anche se la risposta accettata fornisce alcuni buoni esempi, il .Inject e alcuni esempi di Haack non gestiscono l’escape. Molti si basano anche su Regex (più lento) o su DataBinder.Eval che non è disponibile su .NET Core e in altri ambienti.

    Con questo in mente, ho scritto un semplice parser basato sullo stato macchina che scorre attraverso i caratteri, scrivendo su un output StringBuilder , carattere per carattere. È implementato come metodo (i) di estensione String e può prendere sia un Dictionary o object con parametri come input (usando la reflection).

    Gestisce livelli illimitati di {{{escaping}}} e genera FormatException quando l’input contiene parentesi graffe sbilanciate e / o altri errori.

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

    In definitiva, tutta la logica si riduce a 10 stati principali: quando la macchina di stato si trova all’esterno di una staffa e allo stesso modo all’interno di una staffa, il personaggio successivo è una parentesi aperta, una parentesi graffa aperta, una coppia chiusa, una parentesi chiusa sfuggita, o un personaggio ordinario. Ognuna di queste condizioni viene gestita singolarmente man mano che il ciclo procede, aggiungendo caratteri a uno StringBuffer output oa un Key StringBuffer . Quando un parametro viene chiuso, il valore della chiave StringBuffer viene utilizzato per cercare il valore del parametro nel dizionario, che viene quindi inserito nell’uscita StringBuffer . Alla fine, viene restituito il valore dell’output StringBuffer .

     string language = "Python"; int numquotes = 2; string output = language + " has "+ numquotes + " language types."; 

    Modifica: Quello che avrei dovuto dire era: “No, non credo che quello che vuoi fare sia supportato da C #. Questo è il più vicino che hai intenzione di ottenere”.