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

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

Я просто хочу знать, почему ты так ненавидишь Джона?

EBGreen 10.12.2008 23:16

Ну, знаешь, у него ноль кошек. Домашние животные продлевают жизнь. Это наука!

George Mauer 10.12.2008 23:22

Хм, отличная идея, если вы напишете одну, поделитесь ею

JoshBerke 10.12.2008 23:22
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
3
2 747
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

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

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

Это не поможет, поскольку он объединяет более одного экземпляра в одной строке. Также: вы действительно хотите это сделать, или может быть когда-нибудь вам понадобится другая интерпретация .ToString ()?

Joel Coehoorn 10.12.2008 23:19

Я понимаю, о чем вы говорите. Хотя это ужасно грязно. И это довольно ограниченный метод. Было бы проще написать свой собственный String.Format

George Mauer 10.12.2008 23:20

Да, более полезно, если вам нужен способ отображения нескольких свойств в строке. Особенно с использованием интерфейса IFormattable.

JamesSugrue 10.12.2008 23:24

То, что стоит после ":", передается в качестве аргумента методу 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));
    }
}

Я не думаю, что это правда, ToString () не принимает аргументов

George Mauer 10.12.2008 23:21

Хорошая мысль! Существуют перегрузки ToString для многих объектов .NET, которые принимают строку или IFormatProvider в качестве аргументов, но, к сожалению, как указал Джордж Мауэр, они не существуют в Object.

James Orr 10.12.2008 23:26

Это чертовски крутое редактирование. Но да, если вы это имели в виду, это чертовски хорошая идея ...

George Mauer 11.12.2008 00:57

Нет, я не это имел в виду, я просто сначала ошибся :) Я довольно много времени не программировал на C#.

Paolo Tedesco 11.12.2008 00:59

Я правда не понимаю, как это:

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

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

В моем pov String.Format - это своего рода простой движок просмотра, почему он должен быть еще ограничивающим? Если у вас есть стабильная модель предметной области, не должно возникнуть проблем с определением точного формата выходных строк в конфигурации. Было бы довольно удобно.

George Mauer 10.12.2008 23:25

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

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

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

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

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

George Mauer 10.12.2008 23:56

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

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

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

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

var person = new Person { Name = "joe", Email = new Email { Address = "[email protected]" } };

var message = string.Format("{0}'s e-mail is {1}",
    ExpressionEvaluator.GetValue(person, "Name"), 
    ExpressionEvaluator.GetValue(person, "Email.Address"));

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

Ответ принят как подходящий

Обновлено: вам не нужно реализовывать 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.

Вау, Марк. Очень гладко ... действительно очень гладко

George Mauer 11.12.2008 18:26

Проверьте мою библиотеку "Expansive"

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

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

Другие вопросы по теме