NET: Existe uma forma String.Format para inserir o valor de uma propriedade do objeto em uma string?

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

  •  21-08-2019
  •  | 
  •  

Pergunta

Eu acho que a resposta direta à pergunta é 'não', mas eu estou esperando que alguém escreveu uma biblioteca simples real para fazer isso (ou eu posso fazer isso ... ugh ...)

Deixe-me demonstrar o que estou procurando com um exemplo. Suponha que eu tinha o seguinte:

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

Eu gostaria de ser capaz de fazer algo parecido com isto:

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

Alguém sabe se há um formato para fazer algo parecido com isso ou se alguém escreveu uma biblioteca para fazê-lo? Eu sei que não deve ser muito difícil, mas eu prefiro não ser reimplementar a roda.

Foi útil?

Solução

Edit: Você não tem que implementar IFormattable para cada objeto ... que seria uma PITA, limitando severamente, e uma bastante grande carga de manutenção. Basta usar Reflexão e um IFormatProvider com ICustomFormatter e ele vai trabalhar com qualquer objeto. String.Format tem uma sobrecarga de tomar um como um parâmetro.

Eu nunca pensei nisso antes, mas você me intrigou - então eu tive que dar um giro rápido. Note que eu escolhi para permitir uma seqüência de formato adicional a ser passado para o valor da propriedade, e que ele só funciona com propriedades não indexados e acessíveis (embora você poderia facilmente acrescentar que).

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

E o seu exemplo ligeiramente 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);

Saídas:

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.

Outras dicas

Você poderia substituir o ToString () para sua classe.

Boa artigo aqui

O que é após o ":". É passado como um argumento para o método ToString de sua classe
Apenas declarar um método ToString aceitar uma string e 'Nome', 'NumberOfCats' etc. será passado em que parâmetro.

EDIT: Você deve implementar System.IFormattable. Isso 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));
    }
}

Confira minha biblioteca "Expansivo"

Em Nuget aqui: http://nuget.org/List/Packages/Expansive

No GitHub aqui: http://github.com/anderly/expansive

Eu realmente não vejo como isto:

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

é qualquer melhor do que este:

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

Na verdade, desde que você está perdendo intellisense ajuda para o primeiro, não só é mais propenso a falhas, mas provavelmente levar mais tempo para escrever em um IDE.

E se você queria fazer algo como isso, você sempre pode chicotear acima de um método de extensão para ele. Aposto que seria parecido com um pesadelo, tho.

Boo ou Nemerle tem algo como isto. Eu tentei pensar em uma simples maneira agradável para fazer isso por um par de anos agora, com nenhuma resposta fácil.

O melhor que você pode fazer é fornecer-lhe própria IFormatProvider, e analisar a entrada manualmente (a parte baixa qualidade ...).

Se você decidir analisar a cadeia de formato, você deve considerar isso ...:

O projeto Spring.NET tem a Spring.NET Expression Language em Spring.Core. Ele permite que você consulta um gráfico de objeto, apontando para propriedades usando cordas. Usando o seu exemplo, você poderia imaginar algo como isto:

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 provavelmente não armazenar um e-mail assim, mas eu não conseguia pensar em nada melhor)

Isso é algo que muitos fazem no mundo Python usando "% locals SomeString ()." O que você está sugerindo que tem um par de rupturas fundamentais de como funciona a String.format:

  • normalmente a notação espaço reservado tem seqüência de informações de formatação após os dois pontos, ao passo que você quer fazer acesso a propriedade.

  • os índices de espaço reservado ({0, {1, etc) normalmente referem-se aos argumentos Numberes nas args params, mas parece que você gostaria que a sua função de prestar toda a cadeia para cada parâmetro que é passado. eles devem ser concatenadas? retornado como uma matriz de cadeia?

Assim, você pode acabar escrevendo um presente a si mesmo, caso em que você pode ignorar a notação índice inteiramente (assim {NumberOfCats} em vez de {0: NumberOfCats} ou mesmo o uso nome da propriedade seguido de provedor de formato {NumberOfCats: c}. consumir os metadados de um objeto de entrada não deve ser muito difícil.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top