.NET: ¿Hay una forma String.Format para insertar el valor de una propiedad de objeto en una cadena?

StackOverflow https://stackoverflow.com/questions/357447

  •  21-08-2019
  •  | 
  •  

Pregunta

Creo que la respuesta directa a la pregunta es 'no', pero estoy esperando que alguien ha escrito una biblioteca real simple de hacer esto (o lo puedo hacer ... uf ...)

Permítanme demostrar lo que estoy buscando con un ejemplo. Supongamos que tenía la siguiente:

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 es mejor no estaría reimplementar la rueda.

¿Fue útil?

Solución

Editar: Usted no tiene que implementar IFormattable para cada objeto ... que sería un PITA, limitando seriamente, y una bastante grande carga de mantenimiento. Sólo tiene que utilizar la reflexión y una IFormatProvider con ICustomFormatter y que va a trabajar con el cualquier objeto. String.Format tiene una sobrecarga para tomar uno como un parámetro.

Nunca he pensado en esto antes, pero me intrigado - así que tuve que darle un giro rápido. Tenga en cuenta que he elegido para permitir que una cadena de formato adicional que se pasa al valor de la propiedad, y que sólo funciona con propiedades no indexados y accesibles (aunque se puede añadir fácilmente eso).

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.

Otros consejos

Se puede anular el ToString () para su clase.

aquí

Lo que es después de que el ":" se pasa como argumento al método ToString de la clase
. Sólo declarar un método ToString aceptar una cadena, y 'Nombre', etc. '' NumberOfCats será aprobada en ese parámetro.

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

Mira mi biblioteca "expansivo"

En Nuget aquí: http://nuget.org/List/Packages/Expansive

En GitHub aquí: http://github.com/anderly/expansive

Realmente no veo como 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 nada 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 de IntelliSense en el primero de ellos, no sólo es más propenso a fallar pero probablemente necesitará más tiempo para escribir en un IDE.

Y si se quería hacer algo como esto, siempre se puede improvisar un método de extensión por ello. Apuesto a que se vería como una pesadilla, aunque.

Boo o Nemerle tiene algo como esto. He tratado de pensar en una manera simple agradable que lo haga por un par de años, que no tiene respuesta fácil.

Lo mejor que puede hacer es proporcionar IFormatProvider es el propietario, y se analiza la entrada manualmente (la parte cutre ...).

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

El proyecto tiene el Spring.NET Spring.NET lenguaje de expresión en Spring.Core. Se le permite consultar un gráfico de objetos señalando inmueble con cuerdas. Usando su ejemplo, se podría imaginar algo como esto:

var person = new Person { Name = "joe", Email = new Email { Address = "joe@joe.com" } };

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

(i probablemente no almacenar un correo electrónico de esa manera, pero no pude encontrar nada mejor)

Esto es algo que muchos lo hacen en el mundo mediante el uso de Python "% someString locales ()". 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.) se refieren normalmente a los argumentos Numberes en los argumentos params, pero parece que le gustaría que su función para hacer que toda la cadena para cada parámetro que se pasa. deben ser concatenados? devueltos como una matriz de cadenas?

Así que usted puede terminar de escribir esta uno a sí mismo, en cuyo caso se puede omitir la notación índice completo (así {NumberOfCats} en lugar de {0: NumberOfCats} o incluso usar el nombre de propiedad seguido por el proveedor de formato {NumberOfCats: c}. el consumo de los metadatos de un objeto de entrada no debe ser demasiado dura.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top