Почему object.ToString () существует? - PullRequest
11 голосов
/ 13 октября 2009

Разве не намного элегантнее и удобнее иметь IStringable интерфейс?

Кому нужен этот Type.FullName объект, возвращенный нам?

РЕДАКТИРОВАТЬ: все продолжают спрашивать, почему я думаю, что это более элегантно ..

Ну, просто у IComparable объект будет иметь метод CompareTo, который по умолчанию генерирует исключение или возвращает 0.

Существуют объекты, которые нельзя и не следует описывать как строку. объект мог бы в равной степени вернуть string.Empty. Type.FullName это просто произвольный выбор ..

А для таких методов, как Console.Write (объект), я думаю, что это должно быть: Write (IStringable).

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

Кстати, как мне всем вам комментировать? Это нормально, что я отправляю ответ?

Ответы [ 12 ]

19 голосов
/ 13 октября 2009

Наличие Object.ToString делает возможным использование таких API, как Console.WriteLine.

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

Правда, вы можете реализовать Console.WriteLine без Object.ToString и вместо этого выполнить проверку интерфейса и по умолчанию указать полное имя типа, если интерфейс не присутствовал. Но тогда каждый отдельный API, который хотел бы захватить строковое представление экземпляра объекта, должен был бы реализовать эту логику. Учитывая количество раз, когда Object.ToString используется только в ядре BCL, это привело бы к большому дублированию.

19 голосов
/ 13 октября 2009

Существует три виртуальных метода, которые IMHO никогда не должны были добавляться в System.Object ...

  • ToString ()
  • GetHashCode ()
  • Equals ()

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

  1. Если предполагается, что ToString () будет реализован кем-то, использующим ToString () и отображающим результаты, у вас есть неявный контракт, который компилятор не может применить. Вы предполагаете, что ToString () перегружен, но нет способа заставить это иметь место.
  2. С IStringable вам нужно всего лишь добавить это к вашему общему типу-ограничению или извлечь из него ваш интерфейс, чтобы потребовать его использования при реализации объектов.
  3. Если вы получаете выгоду от перегрузки ToString () для отладчика, вам следует начать использовать [System.Diagnostics.DebuggerDisplayAttribute].
  4. Что касается необходимости этой реализации для преобразования объектов в строки с помощью String.Format () и / или Console.WriteLine, они могли бы быть отложены до System.Convert.ToString (object) и проверены на что-то вроде 'IStringable', переключение на имя типа, если оно не реализовано.
  5. Как указывает Кристофер Эстеп, это зависит от культуры.

Так что я думаю, что я один здесь говорю, что ненавижу System.Object и все его виртуальные методы. Но я люблю C # в целом, и в целом, я думаю, дизайнеры проделали большую работу.

Примечание. Если вы намереваетесь зависеть от поведения перегруженного ToString (), я бы посоветовал вам продолжить и определить свой интерфейс IStringable. К сожалению, вам придется выбрать другое имя для метода, если вы действительно хотите его использовать.

более

Мои коллеги и я только что говорили на эту тему. Я думаю, что еще одна большая проблема с ToString () - это ответ на вопрос «для чего он используется?». Это отображаемый текст? Текст сериализации? Отладка текста? Полное имя типа?

3 голосов
/ 13 октября 2009

Совсем нет.

Его не нужно реализовывать и он возвращает специфичные для культуры результаты.

Этот метод возвращает читаемую человеком строку, чувствительную к культуре. Например, для экземпляра класса Double, значение которого равно нулю, реализация Double .. ::. ToString может вернуть «0,00» или «0,00» в зависимости от текущей культуры пользовательского интерфейса.

Кроме того, несмотря на то, что он поставляется со своей собственной реализацией, его можно переопределить, и часто это так.

3 голосов
/ 13 октября 2009

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

1 голос
/ 13 октября 2009

Мммм, так что это может быть переопределено в производных классах, возможно?

1 голос
/ 13 октября 2009

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

0 голосов
/ 13 октября 2009

Действительно, возвращать Type.FullName вам мало смысла, но было бы еще менее полезно, если бы возвращалась пустая строка или ноль. Вы спрашиваете , почему существует. На этот вопрос не так-то просто ответить, и эта проблема обсуждалась годами. Более десяти лет назад несколько новых языков решили, что было бы удобно неявно приводить объект к строке, когда это необходимо, эти языки включают в себя Perl, PHP и JavaScript, но ни один из них не следует полностью парадигме ориентации объекта.

Подходы

У разработчиков объектно-ориентированных языков возникла более сложная проблема. В целом, было три подхода для получения строкового представления объекта:

  • Используйте множественное наследование, просто наследуйте от String, и вы можете быть преобразованы в строку
  • Одиночное наследование: добавьте ToString к базовому классу как виртуальный метод
  • Либо: сделать оператор приведения или конструктор копирования перегружаемым для строк

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

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

Больше истории

Если вы погрузитесь немного глубже в историю, мы увидим, что SmallTalk использует более широкий подход. Базовый объект имеет много других методов, включая printString, printOn и т. Д.

Небольшое десятилетие спустя, когда Бертран Мейер написал свою выдающуюся книгу «Построение объектно-ориентированного программного обеспечения», он предложил использовать довольно широкий базовый класс, GENERAL. Он включает в себя такие методы, как print, print_line и tagged_out, причем последний показывает все свойства объекта, но не имеет значения по умолчанию ToString. Но он предполагает, что "второй базовый объект, ЛЮБОЙ, к которому ведут все определенные пользователем объекты, может быть расширен" , что похоже на прототип, который мы теперь знаем из JavaScript.

В C ++, единственном языке множественного наследования, все еще широко распространенном, общего предка не существует для всех классов. Это может быть лучшим языком кандидата для использования вашего собственного подхода, то есть используйте IStringable. Но у C ++ есть и другие способы: вы можете перегрузить оператор приведения и конструктор копирования, чтобы реализовать реализуемость. На практике необходимость явно указывать на реализацию в строку (как вы предлагаете с IStringable) становится довольно громоздкой. Программисты C ++ знают это.

В Java мы находим первое появление toString для основного языка. К сожалению, у Java есть два основных типа: объекты и типы значений. Типы значений не имеют метода toString, вместо этого вам нужно использовать Integer.toString или привести к объектному аналогу. На протяжении многих лет это оказалось очень громоздким, но программисты на Java (включая меня) научились жить с этим.

Затем появился C # (я пропустил несколько языков, не хочу делать его слишком длинным), который сначала был задуман как язык отображения для платформы .NET, но оказался очень популярным после первоначального скептицизма. Дизайнеры C # (Андерс Хейлсберг и др.) Смотрели в основном на C ++ и Java и пытались взять лучшее из обоих миров. Тип значения остался, но был введен бокс. Это позволило неявно получать типы значений из Object. Добавление ToString, аналогичного Java, было небольшим шагом и было сделано для облегчения перехода из мира Java, но уже продемонстрировало его бесценные достоинства.

Oddity

Хотя вы прямо не спрашиваете об этом, но почему следующее должно потерпеть неудачу?

object o = null;
Console.WriteLine(o.ToString());

и пока вы думаете об этом, подумайте о следующем, что не ошибается:

public static string MakeString(this object o)
{ return o == null ? "null" : o.ToString();  }

// elsewhere:
object o = null;
Console.WriteLine(o.MakeString());

, который заставляет меня задать вопрос: если бы разработчики языка думали о методах расширения на ранней стадии, метод ToString был бы частью методов расширения для предотвращения ненужных исключений NullPointerExceptions? Некоторые считают это плохим дизайном, другие считают, что это экономит время.

У Эйфеля в то время был специальный класс NIL, который представлял собой ничто, но все еще имел все методы базового класса. Иногда мне хотелось, чтобы C # или Java вообще отказались от нуля, как это делал Бертран Мейер.

Заключение

Широкий подход классических языков, таких как Eiffel и Smalltalk, был заменен очень узким подходом. У Java все еще есть много методов для Object, у C # есть только несколько. Это, конечно, хорошо для реализации. Хранение ToString в пакете просто сохраняет программирование чистым и в то же время понятным, а поскольку оно virtual, вы можете (и должны!) Всегда его переопределять, что сделает ваш код более понятным.

- Авель -

EDIT: вопросник отредактировал вопрос и сделал сравнение с IComparable, то же самое, вероятно, верно для ICloneable. Это очень хорошие замечания, и часто считается, что IComparable должен был быть включен в Object. В соответствии с Java, C # имеет Equals, а не IComparable, но в отличие от Java, C # не имеет ICloneable (Java имеет clone()).

Вы также заявляете, что это удобно только для отладки. Что ж, учтите это везде, где вам нужно получить строковую версию чего-либо (надуманного, без дополнительных методов, без String.Format, но вы поняли):

CarInfo car = new CarInfo();
BikeInfo bike = new BikeInfo();
string someInfoText = "Car " +
   (car is IStringable) ? ((IStringable) car).ToString() : "none") +
   ", Bike " + 
   (bike is IStringable) ? ((IStringable) bike).ToString() : "none");

и сравните это с этим. Что бы вам ни показалось легче, вы должны выбрать:

CarInfo car = new CarInfo();
BikeInfo bike = new BikeInfo();
string someInfoText = "Car " + car.ToString() + ", Bike " + bike.ToString();

Помните, что языки делают вещи более ясными и легкими. Многие части языка (LINQ, методы расширения, ToString(), оператор ??) создаются как удобства. Ничего из этого не является необходимым, но мы, конечно, рады, что они у нас есть. Только когда мы знаем, как правильно их использовать, мы также находим истинное значение функции (или нет).

0 голосов
/ 13 октября 2009

Мой новый базовый класс:

class Object : global::System.Object
{
    [Obsolete("Do not use ToString()", true)]
    public sealed override string ToString()
    {
        return base.ToString();
    }

    [Obsolete("Do not use Equals(object)", true)]
    public sealed override bool Equals(object obj)
    {
        return base.Equals(this, obj);
    }

    [Obsolete("Do not use GetHashCode()", true)]
    public sealed override int GetHashCode()
    {
        return base.GetHashCode();
    }
}
0 голосов
/ 13 октября 2009

Я хотел бы добавить пару соображений о том, почему определение класса .NET System.Object имеет метод ToString () или функцию-член, в дополнение к предыдущим публикациям об отладке.

Поскольку .NET Common Language Runtime (CLR) или Runtime Runtime поддерживает Reflection, возможность создания экземпляра объекта с учетом строкового представления типа класса представляется существенной и фундаментальной. И если я не ошибаюсь, все ссылочные значения в CLR являются производными от System.Object, а наличие в классе метода ToString () обеспечивает его доступность и использование через Reflection. Определение и реализация интерфейса в духе IStringable не является обязательным или обязательным при определении класса в .NET и не обеспечивает возможность динамического создания нового экземпляра после запроса сборки для поддерживаемых типов классов.

Поскольку более продвинутые функциональные возможности .NET, доступные в средах выполнения 2.0, 3.0 и 3.5, такие как Generics и LINQ, основаны на Reflection и динамическом создании экземпляров, не говоря уже о поддержке .NET Dynamic Language Runtime (DLR), которая обеспечивает .NET Реализация языков сценариев, таких как Ruby и Python, возможность идентифицировать и создавать экземпляр по строковому типу представляется важной и необходимой функцией, которая должна присутствовать во всех определениях классов.

Короче говоря, если мы не можем идентифицировать и назвать конкретный класс, который мы хотим создать, как мы можем его создать? Использование метода ToString (), который имеет поведение базового класса и возвращает тип класса в виде «читаемой человеком» строки, кажется, имеет смысл.

Возможно, обзор статей и книг Джеффри Ричтера и Дона Бокса о дизайне и архитектуре .NET Framework также поможет лучше понять эту тему.

0 голосов
/ 13 октября 2009

Структуры и объекты имеют член ToString () для упрощения отладки.

Самый простой пример этого можно увидеть с Console.WriteLine, который получает полный список типов, включая объект, но также получает params object[] args. Поскольку консоль часто является слоем поверх TextWriter, эти операторы также полезны (иногда) при записи в файлы и другие потоки (сокеты).

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...