2008-12-10 8 views
6

Creo que la respuesta directa a la pregunta es 'No', pero espero que alguien haya escrito una biblioteca realmente simple para hacer esto (o puedo hacerlo ... ugh ...).NET: ¿Hay un formulario String.Format para insertar el valor de una propiedad de objeto en una cadena?

Déjame demostrar lo que estoy buscando con un ejemplo. Supongamos que tenía los siguientes:

class Person { 
    string Name {get; set;} 
    int NumberOfCats {get; set;} 
    DateTime TimeTheyWillDie {get; set;} 
} 

me gustaría ser capaz de hacer algo como esto:

static void Main() { 
    var p1 = new Person() {Name="John", NumberOfCats=0, TimeTheyWillDie=DateTime.Today}; 
    var p2 = new Person() {Name="Mary", NumberOfCats=50, TimeTheyWIllDie=DateTime.Max}; 

    var str = String.Format(

"{0:Name} has {0:NumberOfCats} cats and {1:Name} has {1:NumberOfCats} cats. They will die {0:TimeTheyWillDie} and {1:TimeTheyWillDie} respectively 
", p1, p2); 

    Console.WriteLine(str); 
} 

¿Alguien sabe si tener un formato para hacer algo como esto o si alguien ha escrito una biblioteca para hacerlo? Sé que no debería ser demasiado difícil, pero preferiría no volver a implementar la rueda.

+0

Solo quiero saber por qué odias a John tanto? – EBGreen

+0

Bueno, eso fue morboso. –

+0

Bueno, ya sabes, él tiene cero gatos. Las mascotas extienden la vida útil. ¡Su ciencia! –

Respuesta

8

Editar: Usted don' Tengo que implementar IFormattable para cada objeto ... que sería un PITA, con una gran limitación y una carga de mantenimiento bastante grande. Simplemente use Reflection y un IFormatProvider con ICustomFormatter y funcionará con cualquier objeto. String.Format tiene una sobrecarga para tomar uno como parámetro.

Nunca había pensado en esto antes, pero me intrigó, así que tuve que darle un giro rápido. Tenga en cuenta que elegí permitir que se pase una cadena de formato adicional al valor de la propiedad, y que solo funciona con propiedades no indexadas y accesibles (aunque podría agregarlo fácilmente).

public class ReflectionFormatProvider : IFormatProvider, ICustomFormatter { 
    public object GetFormat(Type formatType) { 
     return formatType == typeof(ICustomFormatter) ? this : null; 
    } 

    public string Format(string format, object arg, IFormatProvider formatProvider) { 
     string[] formats = (format ?? string.Empty).Split(new char[] { ':' }, 2); 
     string propertyName = formats[0].TrimEnd('}'); 
     string suffix = formats[0].Substring(propertyName.Length); 
     string propertyFormat = formats.Length > 1 ? formats[1] : null; 

     PropertyInfo pi = arg.GetType().GetProperty(propertyName); 
     if (pi == null || pi.GetGetMethod() == null) { 
      // Pass thru 
      return (arg is IFormattable) ? 
       ((IFormattable)arg).ToString(format, formatProvider) 
       : arg.ToString(); 
     } 

     object value = pi.GetGetMethod().Invoke(arg, null); 
     return (propertyFormat == null) ? 
      (value ?? string.Empty).ToString() + suffix 
      : string.Format("{0:" + propertyFormat + "}", value); 
    } 
} 

Y tu ejemplo ligeramente modificado:

var p1 = new Person() {Name="John", NumberOfCats=0, TimeTheyWillDie=DateTime.Today}; 
var p2 = new Person() {Name="Mary", NumberOfCats=50, TimeTheyWillDie=DateTime.MaxValue}; 

var str = string.Format(
    new ReflectionFormatProvider(), 
    @"{0:Name} has {0:NumberOfCats} cats and {1:Name} has {1:NumberOfCats} cats. 
    They will die {0:TimeTheyWillDie:MM/dd/yyyy} and {1:TimeTheyWillDie} respectively. 
    This is a currency: {2:c2}.", 
    p1, 
    p2, 
    8.50M 
); 

Console.WriteLine(str); 

Salidas:

John has 0 cats and Mary has 50 cats. 
They will die 12/10/2008 and 12/31/9999 11:59:59 PM respectively. 
This is a currency: $8.50. 
+0

Wow Mark. Muy resbaladizo ... muy hábil de hecho –

4

Puede anular el ToString() para su clase.

Buen artículo here

+0

No será de ayuda, ya que está combinando más de una instancia en la misma cadena. Además, ¿realmente desea hacer eso, o podría haber alguna otra interpretación .ToString() que pueda querer? –

+0

Ah, veo lo que dices. Eso es horrible, sin embargo. Y es un método bastante limitado. Sería más fácil escribir tu propio String.Format –

+0

Sí, más útil si quieres una forma de exponer múltiples propiedades en una cadena. Especialmente usando la interfaz IFormattable. – JamesSugrue

4

¿Cuál es después de que el ":" se pasa como argumento al método ToString de la clase.
Simplemente declare un método ToString aceptando una cadena, y el 'Nombre', 'NumberOfCats', etc. se pasarán en ese parámetro.

EDITAR: Debe implementar System.IFormattable. Esto funciona:

class Person : IFormattable 
{ 
    public override string ToString() 
    { 
     return "Person"; 
    } 

    public string ToString(string format, IFormatProvider formatProvider) 
    { 
     if (format == "Name") 
     { 
      return "John"; 
     } 
     if (format == "NumberOfCats") 
     { 
      return "12"; 
     } 
     return "Unknown format string"; 
    } 

} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Person p = new Person(); 
     Console.WriteLine(string.Format("Name = {0:Name}",p)); 
     Console.WriteLine(string.Format("NumberOfCats = {0:NumberOfCats}", p)); 
    } 
} 
+0

No creo que esto sea cierto, ToString() no toma ningún argumento –

+0

¡Es una buena idea! Hay sobrecargas de ToString en muchos objetos .NET que toman una cadena o un IFormatProvider como argumentos, pero lamentablemente, como señaló George Mauer, estos no existen en Object. –

+0

Cool. Gracias por compartir. – Guge

1

Realmente no veo cómo esto:

"{0:Name} has {0:NumberOfCats} cats and {1:Name} has {1:NumberOfCats} cats. They will die {0:TimeTheyWillDie} and {1:TimeTheyWillDie} respectively", p1, p2); 

es mejor que esto:

"{0} has {1} cats and {2} has {3} cats. They will die {4} and {5} respectively 
", p1.Name, p1.NumberOfCats, p2.Name, p2.NumberOfCats, p1.TimeTheyWillDie, p2.TimeTheyWillDie); 

De hecho, desde que está perdiendo ayuda en intelisense el primero, no solo es más propenso al fracaso, sino que probablemente demore más tiempo en escribir en un IDE.

Y si quieres hacer algo como esto, siempre puedes utilizar un método de extensión para ello. Apuesto a que se vería como una pesadilla, aunque.

+0

En mi pov String.Format es una especie de motor de vista simple, ¿por qué debería ser más limitante? Si tiene un modelo de dominio estable, no debería haber problemas para definir el formato exacto de las cadenas de salida en una configuración.Sería bastante conveniente. –

0

Esto es algo que muchos hacen en el mundo de Python al usar "someString% locals()." Lo que está sugiriendo, aunque tiene un par de descansos fundamentales de cómo funciona la String.Format:

  • normalmente la notación marcador de posición tiene formato de cadenas de información después de los dos puntos, mientras que usted quiere hacer acceso a la propiedad.

  • los índices de marcador de posición ({0, {1, etc.) normalmente hacen referencia a los argumentos de números en los params args, pero parece que desea que su función muestre toda la cadena para cada parámetro que se pasa. ellos serán concatenados? devuelto como una matriz de cadena?

lo que puede terminar de escribir éste mismo, en cuyo caso se puede omitir la notación índice completo (por lo que {NumberOfCats} en lugar de {0: NumberOfCats} o incluso utilizar el nombre de propiedad, seguido por el proveedor de formato {NumberOfCats : c}. El consumo de los metadatos de un objeto de entrada no debería ser demasiado difícil.

+0

No es así en el punto dos. {0: NumberOfCats} denotaría p1.NumberOfCats donde p1 es el primer argumento posicional. Sin embargo, una buena captura en el primer punto, necesitaría sintaxis adicional –

0

Boo o Nemerle tiene algo como esto.He intentado pensar en una forma sencilla y agradable de hacerlo durante un par de años, sin una respuesta fácil.

Lo mejor que puede hacer es proporcionar su propio IFormatProvider, y analizar la entrada manualmente (la parte de mierda ...).

0

Si decide analizar la cadena de formato de usted, usted debe considerar esto ...:

El proyecto tiene el Spring.NET Spring.NET Expression Language en Spring.Core. Le permite consultar un gráfico de objetos al señalar propiedades usando cadenas. Usando su ejemplo, se podría imaginar algo como esto:

var person = new Person { Name = "joe", Email = new Email { Address = "[email protected]" } }; 

var message = string.Format("{0}'s e-mail is {1}", 
    ExpressionEvaluator.GetValue(person, "Name"), 
    ExpressionEvaluator.GetValue(person, "Email.Address")); 

(que probablemente no volvería almacenar un correo electrónico de esa manera, pero no pude encontrar nada mejor)

Cuestiones relacionadas