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

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

  •  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}.Использование метаданных из входного объекта не должно быть слишком сложным.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top