Formato de cadenas con nombre en C #

¿Hay alguna manera de formatear una cadena por nombre en lugar de posicionarla en C #?

En Python, puedo hacer algo como este ejemplo (descaradamente robado de aquí ):

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

¿Hay alguna manera de hacer esto en C #? Diga por ejemplo:

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

Ser capaz de hacer esto usando un nombre de variable sería agradable, pero también es aceptable un diccionario.

No hay un método incorporado para manejar esto.

Aquí hay un método

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

Aquí está otro

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

Un tercer método mejorado basado parcialmente en los dos anteriores , de Phil Haack

Tengo una implementación que acabo de publicar en mi blog aquí: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx

Aborda algunos problemas que estas otras implementaciones tienen con el paréntesis de escape. La publicación tiene detalles. También hace lo de DataBinder.Eval, pero todavía es muy rápido.

También puede usar tipos anónimos como este:

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

Por supuesto, requeriría más código si también desea analizar el formato, pero puede formatear una cadena usando esta función como:

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

Las cadenas interpoladas se agregaron a C # 6.0 y Visual Basic 14

Ambos fueron presentados a través del nuevo comstackdor de Roslyn en Visual Studio 2015 .

  • C # 6.0:

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

    • fuente: novedades en C # 6.0

  • VB 14:

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

    • fuente: novedades en VB 14

Funciones destacadas (en Visual Studio 2015 IDE):

  • coloreado de syntax es compatible – las variables contenidas en las cadenas están resaltadas
  • La refactorización es compatible: al cambiar el nombre, también se renombran las variables contenidas en las cadenas.
  • en realidad, no solo los nombres de las variables, sino también las expresiones compatibles; por ejemplo, no solo {index} funciona, sino también {(index + 1).ToString().Trim()}

¡Disfrutar! (y haga clic en “Enviar una sonrisa” en el VS)

No parece haber una manera de hacer esto de la caja. Sin embargo, parece factible implementar su propio IFormatProvider que enlaza con IDictionary para valores.

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

Productos:

  Python tiene 2 tipos de cotización 

La advertencia es que no puede mezclar FormatProviders , por lo que el formato de texto elegante no se puede usar al mismo tiempo.

El marco en sí no proporciona una forma de hacerlo, pero puede echar un vistazo a esta publicación de Scott Hanselman. Ejemplo de uso:

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

Este código de James Newton-King es similar y funciona con sub-propiedades e índices,

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

El código de James se basa en System.Web.UI.DataBinder para analizar la cadena y requiere hacer referencia a System.Web, que a algunas personas no les gusta hacer en aplicaciones que no son web.

EDITAR: Ah, y funcionan muy bien con tipos anónimos, si no tiene un objeto con propiedades listas para ello:

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

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

Con la extensión vinculada puede escribir esto:

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

y obtendrá "foo 2 System.Object “.

Creo que lo más cerca que obtendrá es un formato indexado:

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

También hay String.Replace (), si está dispuesto a hacerlo en varios pasos y lo hace con la convicción de que no encontrará sus ‘variables’ en ningún otro lugar de la cadena:

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

Ampliando esto para usar una lista:

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

También puede hacer eso con un Dictionary al iterar sus colecciones .Keys, pero al usar un List > podemos aprovechar el método .ForEach () de List y condensarlo de nuevo en un trazador de líneas:

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

Una lambda sería incluso más simple, pero todavía estoy en .Net 2.0. También tenga en cuenta que el rendimiento .Replace () no es estelar cuando se usa iterativamente, ya que las cadenas en .Net son inmutables. Además, esto requiere que la variable MyString esté definida de tal manera que sea accesible para el delegado, por lo que aún no es perfecta.

Mi biblioteca de código abierto, Regextra , admite el formato con nombre (entre otras cosas). Actualmente se dirige a .NET 4.0+ y está disponible en NuGet . También tengo una entrada de blog introductoria al respecto: Regextra: lo ayuda a reducir sus (problemas) {2} .

El bit de formato con nombre admite:

  • Formato básico
  • Formato de propiedades anidadas
  • Formateo del diccionario
  • Escapando de delimitadores
  • Formato de cadena estándar / personalizado / IFormatProvider

Ejemplo:

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

Resultado:

Acabamos de enviar su pedido de ‘Widget’, colocado el 2/28/2014. Se le facturará a su tarjeta de crédito $ 1,500.00.

Consulte el enlace GitHub del proyecto (arriba) y la wiki para ver otros ejemplos.

Revisa este:

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

Muestra:

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

El rendimiento es bastante bueno en comparación con otras soluciones.

Dudo que esto sea posible. Lo primero que viene a la mente es ¿cómo va a tener acceso a nombres de variables locales?

Sin embargo, puede haber alguna manera inteligente de usar expresiones LINQ y Lambda para hacer esto.

Aquí hay uno que hice hace un tiempo. Extiende String con un método de Formato tomando un solo argumento. Lo bueno es que usará la cadena estándar. Formatee si proporciona un argumento simple como un int, pero si usa algo como el tipo anónimo, también funcionará.

Ejemplo de uso:

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

Resultaría en “La familia Smith tiene 4 hijos”.

No hace cosas vinculantes locas como arreglos e indexadores. Pero es súper simple y de alto rendimiento.

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

Ejemplo:

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

Salida: 你好, wayjet, 今天 是 2011-05-04, 这 是 次 次 18 次 100, 积分 {100.40}

aquí hay un método simple para cualquier objeto:

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

Y aquí cómo usarlo:

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

salida: 2/27/2012

Implementé esto es una clase simple que duplica la funcionalidad de String.Format (excepto cuando se usan clases). Puede usar un diccionario o un tipo para definir campos.

https://github.com/SergueiFedorov/NamedFormatString

C # 6.0 está agregando esta funcionalidad a la especificación del lenguaje, por lo que NamedFormatString es compatible con versiones anteriores.

Resolví esto de una manera ligeramente diferente a las soluciones existentes. Hace el núcleo del reemplazo del artículo nombrado (no el bit de reflexión que algunos han hecho). Es extremadamente rápido y simple … Esta es mi solución:

 ///  /// 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 + ":"); } } 

Se usa de la siguiente manera:

  [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!")); } 

¡Espero que alguien encuentre esto útil!

Aunque la respuesta aceptada brinda algunos buenos ejemplos, el .Inject así como algunos de los ejemplos de Haack no manejan el escape. Muchos también dependen en gran medida de Regex (más lento) o DataBinder.Eval que no está disponible en .NET Core ni en otros entornos.

Con esto en mente, he escrito un analizador basado en máquinas de estado simple que transmite caracteres, escribiendo en una salida de StringBuilder , carácter por carácter. Se implementa como método (s) de extensión de String y puede tomar un Dictionary u object con parámetros como entrada (usando reflexión).

Maneja niveles ilimitados de {{{escaping}}} y lanza FormatException cuando la entrada contiene llaves desbalanceadas y / u otros errores.

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

En definitiva, toda la lógica se reduce a 10 estados principales: cuando la máquina de estados está fuera de un corchete y también dentro de un corchete, el siguiente carácter es un corchete abierto, un corchete abierto con escape, un corsé cerrado, un corsé cerrado con escape, o un personaje ordinario. Cada una de estas condiciones se maneja individualmente a medida que avanza el ciclo, añadiendo caracteres a un StringBuffer salida o a un StringBuffer clave. Cuando se cierra un parámetro, el valor de la clave StringBuffer se utiliza para buscar el valor del parámetro en el diccionario, que luego se inserta en la salida StringBuffer . Al final, se devuelve el valor de la salida StringBuffer .

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

Editar: Lo que debería haber dicho fue: “No, no creo que lo que quieras hacer sea respaldado por C #. Esto es lo más cercano que vas a obtener”.