.NET: Существует ли форма String.Format для вставки значения свойства объекта в строку? - PullRequest
8 голосов
/ 10 декабря 2008

Я думаю, что прямым ответом на вопрос является «Нет», но я надеюсь, что кто-то написал действительно простую библиотеку для этого (или я могу это сделать… тьфу…)

Позвольте мне продемонстрировать, что я ищу, на примере. Предположим, у меня было следующее:

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

Я хотел бы иметь возможность сделать что-то вроде этого:

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

Кто-нибудь знает, существует ли формат для выполнения чего-либо подобного или кто-то написал библиотеку для этого? Я знаю, что это не должно быть слишком сложно, но я бы не стал заново реализовывать колесо.

Ответы [ 8 ]

9 голосов
/ 11 декабря 2008

Редактировать: вам не нужно реализовывать IFormattable для каждого объекта ... это будет PITA, строго ограничивающее и довольно большое бремя обслуживания. Просто используйте Reflection и IFormatProvider с ICustomFormatter, и он будет работать с любым объектом. String.Format имеет перегрузку, чтобы принять его в качестве параметра.

Я никогда не думал об этом раньше, но ты меня заинтриговал - поэтому мне пришлось быстро повернуть его. Обратите внимание, что я решил разрешить передачу дополнительной строки формата в значение свойства и что он работает только с неиндексированными и доступными свойствами (хотя вы можете легко это добавить).

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

И ваш слегка измененный пример:

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

Выходы:

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.
4 голосов
/ 10 декабря 2008

Что после ":" передается в качестве аргумента методу ToString вашего класса.
Просто объявите метод ToString, принимающий строку, и в этом параметре будут переданы «Name», «NumberOfCats» и т. Д.

РЕДАКТИРОВАТЬ: Вы должны реализовать System.IFormattable. Это работает:

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));
    }
}
4 голосов
/ 10 декабря 2008

Вы можете переопределить ToString () для вашего класса.

Хорошая статья здесь

2 голосов
/ 26 октября 2011

Проверьте мою библиотеку "Экспансивная"

На Nuget здесь: http://nuget.org/List/Packages/Expansive

На GitHub здесь: http://github.com/anderly/expansive

1 голос
/ 10 декабря 2008

Я действительно не вижу, как это:

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

лучше, чем это:

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

На самом деле, поскольку вы теряете помощь intellisense в первом случае, она не только более склонна к сбоям, но, вероятно, займет больше времени для записи в IDE.

И если вы хотите сделать что-то подобное, вы всегда можете использовать для этого метод расширения. Могу поспорить, это будет выглядеть как кошмар, хотя.

0 голосов
/ 10 декабря 2008

Если вы решите разобрать строку формата самостоятельно, вам следует учесть это ...:

Проект Spring.NET имеет язык выражений Spring.NET в Spring.Core. Это позволяет вам запрашивать граф объекта, указывая на свойства, используя строки. Используя ваш пример, вы можете представить что-то вроде этого:

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

(я бы, наверное, не хранил такую ​​электронную почту, но не мог придумать ничего лучше)

0 голосов
/ 10 декабря 2008

Бу или Немерле есть что-то вроде этого. Я пытался придумать хороший простой способ сделать это в течение пары лет без простого ответа.

Лучшее, что вы можете сделать, - это предоставить собственный IFormatProvider и проанализировать ввод вручную (дрянная часть ...).

0 голосов
/ 10 декабря 2008

Это то, что многие делают в мире Python, используя "someString% locals ()". То, что вы предлагаете, имеет несколько фундаментальных отличий от работы string.format:

  • обычно нотация заполнителя содержит информацию о форматировании строки после двоеточия, тогда как вы хотите получить доступ к свойству.

  • индексы-заполнители ({0, {1 и т. Д.) Обычно ссылаются на аргументы numberes в аргументах params, но, похоже, вы хотели бы, чтобы ваша функция отображала всю строку для каждого передаваемого параметра. Должны ли они быть соединены? возвращается как строковый массив?

Таким образом, вы можете написать это самостоятельно, в этом случае вы можете полностью пропустить обозначение индекса (например, {NumberOfCats} вместо {0: NumberOfCats} или даже использовать имя свойства, за которым следует поставщик формата {NumberOfCats: c}. Потребление метаданных из входного объекта не должно быть слишком сложным.

...