.NET:Существует ли строка.Форма форматирования для вставки значения свойства объекта в строку?
-
21-08-2019 - |
Вопрос
Я думаю, что прямой ответ на вопрос - "Нет", но я надеюсь, что кто-то написал настоящую простую библиотеку для этого (или я могу это сделать ... тьфу ...)
Позвольте мне продемонстрировать, что я ищу, на примере.Предположим, у меня было следующее:
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);
}
Кто-нибудь знает, существует ли формат для выполнения чего-то подобного или кто-то написал библиотеку для этого?Я знаю, что это не должно быть слишком сложно, но я бы предпочел не переосмысливать колесо.
Решение
Редактировать:Вам не нужно реализовывать IFormattable для каждого объекта...это было бы сложной задачей, серьезно ограничивающей и довольно большой нагрузкой на обслуживание.Просто используйте Reflection и IFormatProvider с ICustomFormatter, и это будет работать с Любой объект.Строка.Формат перегружен, чтобы принимать единицу в качестве параметра.
Я никогда не думал об этом раньше, но вы меня заинтриговали, так что мне пришлось быстро все обдумать.Обратите внимание, что я решил разрешить передачу дополнительной строки формата в значение свойства и что она работает только с неиндексированными и доступными свойствами (хотя вы могли бы легко добавить это).
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.
Другие советы
Вы могли бы переопределить toString() для вашего класса.
Хорошая статья здесь
То, что находится после ":", передается в качестве аргумента методу 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));
}
}
Посмотрите мою "Обширную" библиотеку
О Nuget здесь: http://nuget.org/List/Packages/Expansive
На GitHub здесь: http://github.com/anderly/expansive
Я действительно не понимаю, как это:
"{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.
И если вы хотите сделать что-то подобное, вы всегда можете создать для этого метод расширения.Держу пари, это было бы похоже на кошмарный сон, хотя.
У Boo или Nemerle есть что-то подобное.Вот уже пару лет я пытаюсь придумать хороший и простой способ сделать это, но простого ответа нет.
Лучшее, что вы можете сделать, это предоставить свой собственный IFormatProvider и проанализировать входные данные вручную (дерьмовая часть ...).
Если вы решите разобрать строку формата самостоятельно, вам следует учесть это...:
The Spring.Проект NET имеет Spring.ЧИСТЫЙ язык выражения весной.Сердцевина.Он позволяет вам запрашивать график объектов, указывая на свойства с помощью строк.Используя ваш пример, вы могли бы представить себе что-то вроде этого:
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"));
(я бы, вероятно, не стал хранить такое электронное письмо, но я не смог придумать ничего лучшего)
Это то, что многие делают в мире Python, используя "someString % locals()". Однако то, что вы предлагаете, имеет пару фундаментальных отличий от того, как работает string.format:
обычно обозначение-заполнитель содержит информацию о форматировании строки после двоеточия, тогда как вы хотите выполнить доступ к свойству.
индексы-заполнители ({0, {1 и т.д.) Обычно ссылаются на числовые аргументы в аргументах params, но, похоже, вы хотели бы, чтобы ваша функция отображала всю строку для каждого передаваемого параметра.Должны ли они быть объединены?возвращается в виде массива строк?
Таким образом, вы можете в конечном итоге написать это самостоятельно, и в этом случае вы можете полностью пропустить индексную нотацию (так что {NumberOfCats} вместо {0:NumberOfCats} или даже использовать имя свойства, за которым следует format provider {NumberOfCats: c}.Использование метаданных из входного объекта не должно быть слишком сложным.