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

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

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

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

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

10.12.2008 20:14:24
Я просто хочу знать, почему ты так ненавидишь Джона?
EBGreen 10.12.2008 20:16:52
Ну, вы знаете, у него ноль кошек. Домашние животные увеличивают продолжительность жизни. Это наука!
George Mauer 10.12.2008 20:22:33
Хм, классная идея, если ты напишешь одну, поделись ею
JoshBerke 10.12.2008 20:22:40
8 ОТВЕТОВ
РЕШЕНИЕ

Редактировать: вам не нужно реализовывать 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.
9
5.01.2009 15:54:48
Вау Марк. Очень гладко ... действительно очень гладко
George Mauer 11.12.2008 15:26:42

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

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

4
10.12.2008 20:16:04
Это не поможет, так как он объединяет несколько экземпляров в одной строке. Кроме того: вы действительно хотите это сделать, или может быть какая-то другая интерпретация .ToString (), которая вам может понадобиться?
Joel Coehoorn 10.12.2008 20:19:04
Ах, я понимаю, что вы говорите. Это ужасно грязно, хотя. И это довольно ограниченный метод. Было бы проще написать свой собственный String.Format
George Mauer 10.12.2008 20:20:09
Да, более полезно, если вы хотите получить доступ к нескольким свойствам в строке. Особенно с использованием интерфейса IFormattable.
JamesSugrue 10.12.2008 20:24:43

То, что после «:» передается в качестве аргумента методу 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.12.2008 20:59:04
Я не думаю, что это правда, ToString () не принимает никаких аргументов
George Mauer 10.12.2008 20:21:28
Это хорошая мысль! Существуют перегрузки ToString во многих объектах .NET, которые принимают строку или IFormatProvider в качестве аргументов, но, к сожалению, как отметил Джордж Мауэр, их нет в Object.
James Orr 10.12.2008 20:26:12
Это чертовски правка. Но да, если ты это имел в виду, то это чертовски хорошая идея ...
George Mauer 10.12.2008 21:57:14
Нет, это не то, что я имел в виду, я просто ошибся в первом случае :) Довольно часто я не программировал на C #.
Paolo Tedesco 10.12.2008 21:59:33

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

"{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.

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

1
10.12.2008 21:19:16
В моем pov String.Format - это своего рода простой механизм просмотра, почему он должен быть более ограничивающим? Если у вас стабильная модель предметной области, не должно быть проблем с определением точного формата выходных строк в конфигурации. Это было бы довольно удобно.
George Mauer 10.12.2008 20:25:16

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

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

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

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

0
10.12.2008 20:28:20
Не так во втором пункте. {0: NumberOfCats} будет обозначать p1.NumberOfCats, где p1 - первый позиционный аргумент. Хороший улов по первому пункту, хотя, действительно, потребуется некоторый дополнительный синтаксис
George Mauer 10.12.2008 20:56:31

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

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

0
10.12.2008 20:25:55

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

Проект 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.12.2008 20:28:18

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

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

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

2
27.10.2011 06:40:48