Почему / когда было бы целесообразно переопределить ToString?

103

Я изучаю C #, и мне интересно, в чем смысл и преимущества переопределения ToString, как показано в примере ниже.

Можно ли сделать это более простым способом, используя обычный метод без переопределения?

public string GetToStringItemsHeadings
{
    get { return string.Format("{0,-20} {1, -20}", "Office Email", "Private Email"); }
}


public override string ToString()
{
    string strOut = string.Format("{0,-20} {1, -20}", m_work, m_personal);
    return strOut;
}
3D-креатив
источник
4
Зависит от того, действительно ли вы хотите использовать его для отображения объекта в пользовательском интерфейсе в любом месте, иначе это обычно просто для отладки - вы увидите вывод ToString в окне просмотра VS. (Но вы также можете добиться этого с помощью атрибутов класса.) Учитывая, что у вас есть заголовки и выходные данные, похоже, что эта программа использует их для выгрузки объекта с помощью Console.WriteLine(obj.GetToStringItemsHeadings); Console.WriteLine(obj);или подобного.
Rup
2
Я не совсем понимаю вопрос. Можно ли иначе? Да. Но почему ты хочешь сделать по-другому.
Конрад Рудольф
5
Я бы действительно пропустил GetToStringназвание свойства ... Тогда вы получите, например,Console.WriteLine(obj.ItemHeadings)
Svish
и измените свойство ItemHeadings на статическое, так как ему не нужно никакого «this».
eFloh
Вы можете проверить этот вопрос: stackoverflow.com/questions/7906828/oo-design-advice-tostring
Юрий Генсев,

Ответы:

127
  • Вам нужно переопределить ToString? Нет.

  • Можете ли вы получить строковое представление вашего объекта другим способом? Да.

Но при использовании ToStringвы используете метод, общий для всех объектов, и поэтому другие классы знают об этом методе. Например, когда платформа .NET хочет преобразовать объект в строковое представление, ToStringэто главный кандидат (есть и другие, если вы хотите предоставить более сложные параметры форматирования).

Конкретно,

Console.WriteLine(yourObject);

призовет yourObject.ToString().

Конрад Рудольф
источник
13
Да. Также это очень полезно при разработке MVC или ASP.Net, где @yourObjectи <%=yourObject%>будет "ToString-ed".
Бен Леш
4
Также при заполнении поля со списком ToString является вызовом по умолчанию для получения текста элемента
Дави Фиаменги
3
Остерегайтесь такой практики. Реальная история: студент переопределил ToString с помощью метода, который помимо возврата строки изменил данные. Он отлаживал программу, но пока программа стояла в точке останова, значение продолжало меняться. Очевидно - каждый раз, когда он проверял, выполнялось значение ToString, поэтому значение менялось, даже если программа не работала.
JNF
3
@JNF Что такое «такая практика»? ToStringне должен фактически изменять состояние объекта. Но я выступал не за это.
Конрад Рудольф
8
@JNF Я с этим не согласен. ToStringэто означает быть переопределены. Период. Оговаривать это утверждение непродуктивно. Если ты ошибаешься - твоя вина. Фактически, ваш ученик нарушил пункт 4 официального списка рекомендаций по переопределению, ToStringкоторый был опубликован в ответе Дэвида Андерсона.
Конрад Рудольф
129

Я просто дам вам ответ прямо из Руководства по проектированию инфраструктуры из серии .NET Development Series.

ИЗБЕГАЙТЕ исключения исключений изToString

РАССМАТРИВАЙТЕ возврат уникальной строки, связанной с экземпляром.

СЧИТАЙТЕ , что выходные данные ToStringявляются допустимыми входными данными для любых методов синтаксического анализа этого типа.

ОБЯЗАТЕЛЬНО убедитесь, что у ToStringнего нет наблюдаемых побочных эффектов.

НЕОБХОДИМО сообщать конфиденциальную информацию о безопасности путем переопределения ToStringтолько после запроса соответствующего разрешения. Если запрос разрешения не выполняется, верните строку, исключающую конфиденциальную информацию.

Object.ToStringМетод предназначен для использования для общего отображения и отладки. Реализация по умолчанию просто предоставляет имя типа объекта. Реализация по умолчанию не очень полезна, и рекомендуется переопределить метод.

НЕОБХОДИМО переопределить ToStringвсякий раз, когда может быть возвращена интересная для человека строка. Реализация по умолчанию не очень полезна, а индивидуальная реализация почти всегда может обеспечить большую ценность.

НЕОБХОДИМО предпочитать понятное имя уникальному, но не читаемому идентификатору.

Также стоит упомянуть, поскольку Крис Селлс также объясняет в рекомендациях, что ToStringчасто опасно для пользовательских интерфейсов. Как правило, мое практическое правило - предоставить свойство, которое будет использоваться для привязки информации к пользовательскому интерфейсу, и оставить разработчику ToStringпереопределение для отображения диагностической информации. Вы также можете украсить свой шрифт DebuggerDisplayAttribute.

ДЕЙСТВИТЕЛЬНО пытайтесь сохранить строку, возвращаемую из ToStringкороткой. Отладчик использует ToStringдля получения текстового представления объекта, который будет показан разработчику. Если строка длиннее, чем может отобразить отладчик, процесс отладки затруднен.

Форматирование строки DO на основе языка и региональных параметров текущего потока при возврате информации, зависящей от языка и региональных параметров.

НЕОБХОДИМО предоставлять перегрузку ToString(string format)или реализовывать IFormattable, если строка, возвращаемая из, ToStringзависит от языка и региональных параметров или существуют различные способы форматирования строки. Например, DateTimeпредоставляет перегруз и орудия IFormattable.

НЕ возвращайте пустую строку или ноль изToString

Я клянусь этими правилами, и вы должны это сделать. Я не могу сказать вам, как мой код улучшился только благодаря этому руководству для ToString. То же самое IEquatable(Of T)и с такими вещами, как и IComparable(Of T). Эти вещи делают ваш код очень функциональным, и вы не пожалеете, что потратили дополнительное время на его реализацию.

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

Дэвид Андерсон
источник
6
Хороший ответ, это рекомендации, на которые вы ссылаетесь msdn.microsoft.com/en-us/library/ms229042.aspx ?
Джодрелл
2
@Jodrell Я считаю, что это взято из Руководства по дизайну фреймворка
Джим Шуберт,
6
Нужна лучшая цитата.
Бен Фойгт
13
Я не знал, что ТАК превращается в Википедию.
Дэвид Андерсон
14
Это не так, но когда вы цитируете руководящие принципы, предоставление источника - несомненный плюс.
Falanwe
39

Переопределение ToString()позволяет вам дать удобное, удобочитаемое строковое представление класса.

Это означает, что вывод может раскрыть полезную информацию о вашем классе. Например, если у вас есть класс Person, вы можете выбрать ToString()вывод идентификатора человека, его имени, фамилии и т. Д. Это чрезвычайно полезно при отладке или регистрации.

Что касается вашего примера - трудно сказать, полезно ли ваше переопределение, не зная, что это за класс, но сама реализация в порядке.

Роб Левин
источник
13

Это всегда уместно, но внимательно рассматривайте намерения, стоящие за тем, что вы показываете.

Лучше спросить:

Зачем переопределять ToString ()?

ToString () - это окно в состояние объекта. Упор на состояние как требование. Языки с сильным ООП, такие как Java / C #, злоупотребляют моделью ООП, инкапсулируя все в классе. Представьте, что вы пишете код на языке, который не следует строгой модели ООП; подумайте, будете ли вы использовать класс или функцию. Если вы будете использовать его как функцию (например, глагол, действие), а внутреннее состояние поддерживается только временно между вводом / выводом, ToString () не будет добавлять значение.

Как уже упоминалось другими, важно учитывать то, что вы выводите с помощью ToString (), потому что это может использоваться отладчиком или другими системами.

Мне нравится представлять метод ToString как параметр --help объекта. Он должен быть коротким, читаемым, очевидным и легко отображаемым. Он должен отображать то, что объект не является тем, что он делает . Имея это в виду, давайте рассмотрим ...

Пример использования - разбор TCP-пакета:

Не захват сети только на уровне приложения, а что-то более содержательное, например захват pcap.

Вы хотите перегрузить ToString () только для уровня TCP, чтобы вы могли печатать данные на консоли. Что в него входит? Вы можете сойти с ума и проанализировать все детали TCP (т.е. TCP сложен) ...

Что включает в себя:

  • Исходный порт
  • Порт назначения
  • Последовательность чисел
  • Номер подтверждения
  • Смещение данных
  • Флаги
  • Смещение окна
  • Контрольная сумма
  • Срочный указатель
  • Варианты (я даже туда не пойду)

Но хотели бы вы получать весь этот мусор, если бы вызывали TCP.ToString () для 100 пакетов? Конечно, нет, это будет информационная перегрузка. Простой и очевидный выбор также самый разумный ...

Раскройте то, что люди ожидают увидеть:

  • Исходный порт
  • Порт назначения

Я предпочитаю разумный вывод, который легко анализировать людям, но не YMMV .

TCP:[destination:000, source:000]

Ничего сложного, вывод не предназначен для анализа машинами (то есть, если люди не злоупотребляют вашим кодом), предполагаемая цель - удобочитаемость человека.

Но как насчет всей остальной той пикантной информации, о которой я говорил раньше, разве это тоже не полезно? Я перейду к этому, но сначала ...


ToString () один из самых ценных и малоиспользуемых методов всех времен

По двум причинам:

  1. Люди не понимают, для чего нужен ToString ()
  2. В базовом классе Object отсутствует другой, не менее важный строковый метод.

Причина 1. Не злоупотребляйте полезностью ToString ():

Многие люди используют ToString () для получения простого строкового представления объекта. В руководстве по C # даже говорится:

ToString - это основной метод форматирования в .NET Framework. Он преобразует объект в его строковое представление, чтобы он был пригоден для отображения.

Отображение, без дальнейшей обработки. Это не значит, что возьмите мое красивое строковое представление TCP-пакета выше и извлеките исходный порт с помощью regex :: cringe ::.

Право способ сделать вещи есть, ToString вызов () непосредственно на свойстве SourcePort (которая кстати является USHORT так ToString () уже должны быть доступны).

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

К счастью, очень распространены такие стратегии:

  • ISerializable (C #)
  • Рассол (Python)
  • JSON (Javascript или любой язык, на котором он реализован)
  • МЫЛО
  • и т.д...

Примечание: если вы не используете PHP, потому что, herp-derp, для этого есть функция :: snicker ::

Причина 2 - ToString () недостаточно:

Мне еще предстоит увидеть язык, реализующий это в основе, но я видел и использовал варианты этого подхода в дикой природе.

Некоторые из них включают:

  • ToVerboseString ()
  • ToString (verbose = true)

По сути, этот беспорядок состояния TCP-пакета должен быть описан для удобства чтения человеком. Чтобы не «бить мертвую лошадь», говоря о TCP, я «укажу пальцем» на случай № 1, когда я думаю, что ToString () и ToVerboseString () используются недостаточно ...

Пример использования - массивы:

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

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

Существуют различные случаи , когда языки используют общие хак и некоторые , которые получают это право . Некоторые требуют повторного изобретения колеса, некоторые делают неглубокий сброс, другие делают глубокий сброс, ни один из них не работает так, как я бы хотел ...

Я прошу очень простого подхода:

print(array.ToString());

Вывод: 'Array [x]' или 'Array [x] [y]'.

Где x - количество элементов в первом измерении, а y - количество элементов во втором измерении или какое-то значение, которое указывает на то, что 2-е измерение неровно (возможно, диапазон мин / макс?).

И:

print(array.ToVerboseString());

Выводит целую челку красивым шрифтом, потому что я ценю красивые вещи.

Надеюсь, это проливает свет на тему, которая давно меня раздражает. По крайней мере, я добавил небольшую приманку для троллей, чтобы PHP-специалисты проголосовали против этого ответа.

Эван Плэйс
источник
Вы написали: «Я еще не видел языка, который реализует это в основе». Мне кажется, что Python очень близок к этому; например, при печати массива все данные отображаются в сжатом виде. Это единственное, чего мне не хватает в C # / Java, хотя мне нравится их строгость. И когда вам нужно немного выйти за рамки ToString (), было бы неплохо иметь что-то вроде синтаксиса понимания списка Python; Я думаю, что string.Join () похожа, но менее гибкая.
Джон Кумбс
1
@JCoombs Я не возражаю, понимание отличное. Обход структур данных в Python через понимание значительно упрощает жизнь, но этот подход по-прежнему требует глубоких знаний внутренней структуры объекта. То, что не всегда очевидно с классами, импортированными из внешней библиотеки. Для авторов библиотеки хорошей практикой является переопределение ToString () и предоставление полезного удобочитаемого представления, но также было бы хорошо иметь общий метод дампа, который выводит структуру объекта в общем структурированном удобочитаемом формате (например, JSON).
Evan Plaice
Отличный момент. Поэтому вместо того, чтобы показывать ничего, кроме typeof, он может автоматически сериализоваться во что-то вроде JSON. (До сих пор я видел только объекты .NET, сериализованные в XML, но я новичок в .NET). Но тогда опасность может заключаться в искушении рассматривать ToString () как машиночитаемый? (Например, ожидайте, что у вас будет возможность десериализовать его.)
Джон Кумбс,
1
@JCoombs Да, на функциональном уровне JSON и XML служат той же цели. Многие люди используют ToString как машиночитаемый вывод; плохо это или нет - спорный вопрос. Моя самая большая претензия - зачастую - мучительно выводить состояние объекта в удобочитаемом формате. Реализация ToString () часто используется недостаточно, и чтение состояния объекта вне списков наблюдения отладчика является проблемой. Сериализованные представления хороши в качестве резервной копии для отображения всего состояния объекта, но лучшие реализации ToString - лучший вариант.
Эван Плэйс,
Если ToString()он предназначен для отладки, то почему это единственный способ извлечь весь текст из StringBuilder - важной функции?
Дэн В.
9

На самом деле, речь идет о хорошей практике.

ToString()используется во многих местах для возврата строкового представления объекта, как правило, для использования человеком. Часто ту же строку можно использовать для регидратации объекта (подумайте intили, DateTimeнапример), но это не всегда дано (например, дерево может иметь полезное строковое представление, которое просто отображает счетчик, но, очевидно, вы не можете использовать чтобы восстановить его).

В частности, отладчик будет использовать это для отображения переменной в окнах просмотра, немедленных окнах и т. Д., Поэтому ToStringна самом деле неоценим для отладки.

В общем, такой тип также часто имеет явный член, который также возвращает строку. Например, пара широта / долгота может иметь, например, ToDecimalDegreesвозврат "-10, 50", но он также может иметь ToDegreesMinutesSeconds, поскольку это другой формат для пары широта / долгота. Затем этот же тип может быть заменен ToStringодним из тех, которые предоставят значение по умолчанию для таких вещей, как отладка, или даже потенциально для таких вещей, как рендеринг веб-страниц (например, @конструкция в Razor записывает ToString()результат нестрокового выражения в выходной поток).

Андраш Золтан
источник
6

object.ToString()преобразует объект в его строковое представление. Если вы хотите изменить то, что возвращается, когда пользователь вызывает ToString()созданный вами класс, вам нужно будет переопределить ToString()в этом классе.

Робби
источник
6

Хотя я думаю, что наиболее полезная информация уже предоставлена, я добавлю свои два цента:

  • ToString()предназначен для отмены. Его реализация по умолчанию возвращает имя типа, которое, хотя иногда может быть полезно (особенно при работе с большим количеством объектов), в большинстве случаев оказывается недостаточным.

  • Помните, что в целях отладки вы можете положиться на DebuggerDisplayAttribute. Вы можете прочитать об этом здесь .

  • Как правило, на POCO вы всегда можете переопределить ToString(). POCO - это структурированное представление данных, которое обычно может быть строкой.

  • Создайте ToString как текстовое представление вашего объекта. Может быть, его основные поля и данные, может быть, описание количества элементов в коллекции и т. Д.

  • Всегда старайтесь уместить эту строку в одну строку и иметь только самую важную информацию. Если у вас есть Personкласс со свойствами «Имя», «Адрес», «Номер» и т. Д., Возвращайте только основные данные (укажите какой-либо идентификационный номер).

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

Не бойтесь использовать ToString(). Единственное, о чем я буду осторожен, - это возвращать конфиденциальную информацию. В остальном риск минимален. Конечно, как отметили некоторые, другие классы будут использовать ваш ToString при обращении за информацией. Но черт возьми, когда возвращение имени типа будет считаться лучше, чем получение какой-либо фактической информации?

Бруно Брант
источник
5

Если вы не переопределите, ToStringвы получите реализацию базовых классов, которая Objectявляется кратким именем типа класса.

Если вам нужна другая, более значимая или полезная реализация, ToStringпереопределите ее.


Это может быть полезно при использовании списка вашего типа в качестве источника данных для a, ListBoxпоскольку ToStringон будет отображаться автоматически.

Другая ситуация возникает, когда вы хотите передать свой тип, String.Formatкоторый вызывает, ToStringчтобы получить представление вашего типа.

Джодрелл
источник
4

То, что еще никто не упомянул: переопределив ToString(), вы также можете рассмотреть возможность реализации, IFormattableчтобы вы могли сделать следующее:

 public override ToString() {
   return string.Format("{0,-20} {1, -20}", m_work, m_personal);
 }

 public ToString(string formatter) {
   string formattedEmail = this.ToString();
   switch (formatter.ToLower()) {
     case "w":
       formattedEmail = m_Work;
       break;
     case "p":
       formattedEmail = m_Personal;
       break;
     case "hw":
       formattedEmail = string.Format("mailto:{0}", m_Work);
       break;
   }
   return formattedEmail;
}

Что может быть полезно.

Жап - Бен Дугид
источник
Не могли бы вы привести пример класса фреймворка, предоставляющего этот метод для переопределения? Единственное, что мне известно о фреймворке, - это IFormattibleинтерфейс.
tm1
@ tm1 Любой объект, который наследуется от, System.Objectбудет иметь переопределяемый ToString()метод, но вы правы в том, что метод форматирования не должен быть overrideи действительно должен ссылаться на IFormattibleинтерфейс для полного соответствия.
Zhaph - Бен Дугид
Почти - IFormattableпринимает IFormatProviderпараметр. Тем не менее, спасибо за редактирование вашего сообщения.
tm1
Действительно, у него есть два метода, значение, которое я выразил, было в показанном;)
Zhaph - Ben Duguid
3

Одним из преимуществ переопределения ToString () является поддержка инструментов Resharper: Alt + Ins -> «Форматирование элементов», и он записывает ToString () за вас.

Дэниел Джеймс Брайарс
источник
2

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

филолог
источник
1

При определении структур (эффективно пользовательские примитивы) Я считаю , что это хорошая практика , чтобы иметь согласование ToString, а Parseи TryParseметоды, в частности , для XML сериализации. В этом случае вы будете преобразовывать все состояние в строку, чтобы ее можно было прочитать позже.

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

Кроме того, как сказал Робби, переопределение ToStringпозволяет вам вызывать ToStringтакую ​​же базовую ссылку, как тип object.

Дэйв Кузино
источник
1

Вы можете использовать это, когда у вас есть объект, не имеющий интуитивного значения строкового представления, например person. Поэтому, если вам нужно, например, распечатать этого человека, вы можете использовать это переопределение для подготовки его формата.

NickF
источник
1

Этот Object.ToStringметод следует использовать только для отладки. Реализация по умолчанию показывает имя типа объекта, что не очень полезно. Рассмотрите возможность переопределения этого метода, чтобы предоставить более точную информацию для диагностики и отладки. Учтите, что инфраструктуры ведения журналов также часто используют метод ToString, поэтому вы найдете эти текстовые фрагменты в своих файлах журналов.

Не возвращайте локализованные текстовые ресурсы в Object.ToStringметоде. Причина в том, что метод ToString всегда должен возвращать то, что может понять разработчик. Разработчик может не говорить на всех языках, поддерживаемых приложением.

Реализовать на IFormattableинтерфейс , если вы хотите , чтобы вернуть удобный локализованный текст. Этот интерфейс определяет перегрузку ToString с параметрами formatи formatProvider. FormatProvider помогает форматировать текст с учетом культурных особенностей.

См. Также: Object.ToString и IFormattable

jbe
источник
0

Проще зависит от того, как ваша собственность будет использоваться. Если вам просто нужно отформатировать строку один раз, тогда нет особого смысла переопределять ее.

Однако похоже, что вы переопределяете метод ToString, чтобы он не возвращал обычные строковые данные для вашего свойства, а выполнял стандартный шаблон форматирования. Поскольку вы используете string.format с заполнением.

Поскольку вы сказали, что учитесь, это упражнение также затрагивает основные принципы объектно-ориентированного программирования, касающиеся инкапсуляции и повторного использования кода.

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

Отличный вопрос, а также отличные ответы!

Программное обеспечениеCarpenter
источник
0

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

Но в соответствии с тем, что было сказано ранее, это дает удобочитаемое представление рассматриваемого объекта.

Ранье
источник
0

Я доволен рекомендациями по структуре, упомянутыми в других ответах. Однако я хотел бы выделить цели отображения и отладки.

Будьте осторожны с тем, как вы используете ToStringв своем коде. Ваш код не должен полагаться на строковое представление объекта. Если это так, вы обязательно должны предоставить соответствующие Parseметоды.

Поскольку ToStringего можно использовать везде, это может стать проблемой, связанной с ремонтопригодностью, если вы захотите изменить строковое представление объекта в более поздний момент времени. В этом случае вы не можете просто изучить иерархию вызовов, чтобы выяснить, не сломается ли какой-то код.

tm1
источник