Tostring c что это
Все классы в .NET, даже те, которые мы сами создаем, а также базовые типы, такие как System.Int32 , являются неявно производными от класса Object. Даже если мы не указываем класс Object в качестве базового, по умолчанию неявно класс Object все равно стоит на вершине иерархии наследования. Поэтому все типы и классы могут реализовать те методы, которые определены в классе System.Object. Рассмотрим эти методы.
ToString
Метод ToString служит для получения строкового представления данного объекта. Для базовых типов просто будет выводиться их строковое значение:
int i = 5; Console.WriteLine(i.ToString()); // выведет число 5 double d = 3.5; Console.WriteLine(d.ToString()); // выведет число 3,5
Для классов же этот метод выводит полное название класса с указанием пространства имен, в котором определен этот класс. И мы можем переопределить данный метод. Посмотрим на примере:
Person person = new Person < Name = "Tom" >; Console.WriteLine(person.ToString()); // выведет название класса Person Clock clock = new Clock < Hours = 15, Minutes = 34, Seconds = 53 >; Console.WriteLine(clock.ToString()); // выведет 15:34:53 class Clock < public int Hours < get; set; >public int Minutes < get; set; >public int Seconds < get; set; >public override string ToString() < return $"::"; > > class Person < public string Name < get; set; >= ""; >
Для переопределения метода ToString() в классе Clock, который представляет часы, используется ключевое слово override (как и при обычном переопределении виртуальных или абстрактных методов). В данном случае метод ToString() выводит в строке значения свойств Hours, Minutes, Seconds.
Класс Person не переопределяет метод ToString, поэтому для этого класса срабатывает стандартная реализация этого метода, которая выводит просто название класса.
Кстати в данном случае мы могли задействовать обе реализации:
Person tom = new Person < Name = "Tom" >; Console.WriteLine(tom.ToString()); // Tom Person undefined = new Person(); Console.WriteLine(undefined.ToString()); // Person class Person < public string Name < get; set; >= ""; public override string? ToString() < if (string.IsNullOrEmpty(Name)) return base.ToString(); return Name; >>
То есть если имя — свойство Name не имеет значения, оно представляет пустую строку, то возвращается базовая реализация — название класса. Стоит отметить, что базовая реализация возвращает не просто строку, а объект string? — то есть это может быть строка string, либо значение null , которое указывает на отсутствие значения. И в реальности в качестве возвращаемого типа для метода мы можем использовать как string , так и string?
Если же имя у объекта Person установлено, то возвращается значение свойства Name. Для проверки строки на наличие значения применяется метод String.IsNullOrEmpty() .
Стоит отметить, что различные технологии на платформе .NET активно используют метод ToString для разных целей. В частности, тот же метод Console.WriteLine() по умолчанию выводит именно строковое представление объекта. Поэтому, если нам надо вывести строковое представление объекта на консоль, то при передаче объекта в метод Console.WriteLine необязательно использовать метод ToString() — он вызывается неявно:
Person person = new Person < Name = "Tom" >; Console.WriteLine(person); // Tom Clock clock = new Clock < Hours = 15, Minutes = 34, Seconds = 53 >; Console.WriteLine(clock); // выведет 15:34:53
Метод GetHashCode
Метод GetHashCode позволяет возвратить некоторое числовое значение, которое будет соответствовать данному объекту или его хэш-код. По данному числу, например, можно сравнивать объекты. Можно определять самые разные алгоритмы генерации подобного числа или взять реализацию базового типа:
class Person < public string Name < get; set; >= ""; public override int GetHashCode() < return Name.GetHashCode(); >>
В данном случае метод GetHashCode возвращает хеш-код для значения свойства Name. То есть два объекта Person, которые имеют одно и то же имя, будут возвращать один и тот же хеш-код. Однако в реальности алгоритм может быть самым различным.
Получение типа объекта и метод GetType
Метод GetType позволяет получить тип данного объекта:
Person person = new Person < Name = "Tom" >; Console.WriteLine(person.GetType()); // Person
Этот метод возвращает объект Type , то есть тип объекта.
С помощью ключевого слова typeof мы получаем тип класса и сравниваем его с типом объекта. И если этот объект представляет тип Person, то выполняем определенные действия.
object person = new Person < Name = "Tom" >; if (person.GetType() == typeof(Person)) Console.WriteLine("Это реально класс Person");
Причем поскольку класс Object является базовым типом для всех классов, то мы можем переменной типа object присвоить объект любого типа. Однако для этой переменной метод GetType все равно вернет тот тип, на объект которого ссылается переменная. То есть в данном случае объект типа Person.
Стоит отметить, что проверку типа в примере выше можно сократить с помощью оператора is :
object person = new Person < Name = "Tom" >; if (person is Person) Console.WriteLine("Это реально класс Person");
В отличие от методов ToString, Equals, GetHashCode метод GetType() не переопределяется.
Метод Equals
Метод Equals позволяет сравнить два объекта на равенство. В качестве параметра он принимает объект для сравнения в виде типа object и возврашает true , если оба объекта равны:
public override bool Equals(object? obj)
Например, реализуем данный метод в классе Person:
class Person < public string Name < get; set; >= ""; public override bool Equals(object? obj) < // если параметр метода представляет тип Person // то возвращаем true, если имена совпадают if (obj is Person person) return Name == person.Name; return false; >// вместе с методом Equals следует реализовать метод GetHashCode public override int GetHashCode() => Name.GetHashCode(); >
Метод Equals принимает в качестве параметра объект любого типа, который мы затем приводим к текущему классу — классу Person.
Если переданный объект представляет тип Person, то возвращаем результат сравнения имен двух объектов Person. Если же объект представляет другой тип, то возвращается false.
В данном случае для примера применяется довольно простой алгоритм сравнения, однако при необходимости реализацию метода можно сделать более сложной, например, сравнивать по нескольким свойствам при их наличии.
Стоит отметить, что вместе с методом Equals следует реализовать метод GetHashCode.
var person1 = new Person < Name = "Tom" >; var person2 = new Person < Name = "Bob" >; var person3 = new Person < Name = "Tom" >; bool person1EqualsPerson2 = person1.Equals(person2); // false bool person1EqualsPerson3 = person1.Equals(person3); // true Console.WriteLine(person1EqualsPerson2); // false Console.WriteLine(person1EqualsPerson3); // true
И если следует сравнивать два сложных объекта, как в данном случае, то лучше использовать метод Equals, а не стандартную операцию ==.
C# .ToString() – неявное ускорение
Сразу хочется скачать, что речь пойдет исключительно о личном мнении и практике, которая помогла ускорить то, о чем ранее как-то не сильно приходилось задумываться.
Работая над проектом с многочисленными вычислениями и необходимостью приводить результаты к стринг, довелось делать различные замеры и проверки того, что там и как работает.
Благодаря небезызвестному доттрейсу, удалось заметить некоторое «оттягивание» времени на .ToString().
Обычная привычка, наверное, многим знакомая, если вдруг что, пишем:
const int i = 424242; string str = i.ToString(); Console.WriteLine(str);
И лично я не задумывался над тем, что такой подход при многократных выполнениях может быть медлительным.
Понятное дело, что речь не идет о слишком существенных «тормозах», но если можно как-то просто оптимизировать с виду вполне оптимизированный метод, почему бы и да?
Для проверки нескольких гипотез были сделаны следующие методы:
Тестовые методы
public class ToStringTest < public const int MaxCount = 1000000; public string a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8, a_9, a_10; [Benchmark(Description = "CultureInfoIn")] public void CultureInfoIn() < for (int i = 0; i < MaxCount; i++) < a_1 = i.ToString(CultureInfo.CurrentCulture); a_2 = i.ToString(CultureInfo.CurrentCulture); a_3 = i.ToString(CultureInfo.CurrentCulture); a_4 = i.ToString(CultureInfo.CurrentCulture); a_5 = i.ToString(CultureInfo.CurrentCulture); a_6 = i.ToString(CultureInfo.CurrentCulture); a_7 = i.ToString(CultureInfo.CurrentCulture); a_8 = i.ToString(CultureInfo.CurrentCulture); a_9 = i.ToString(CultureInfo.CurrentCulture); a_10 = i.ToString(CultureInfo.CurrentCulture); >> [Benchmark(Description = "CultureInfoOut")] public void CultureInfoOut() < CultureInfo culture = CultureInfo.CurrentCulture; for (int i = 0; i < MaxCount; i++) < a_1 = i.ToString(culture); a_2 = i.ToString(culture); a_3 = i.ToString(culture); a_4 = i.ToString(culture); a_5 = i.ToString(culture); a_6 = i.ToString(culture); a_7 = i.ToString(culture); a_8 = i.ToString(culture); a_9 = i.ToString(culture); a_10 = i.ToString(culture); >> [Benchmark(Description = "NumberFormatInfoIn")] public void NumberFormatInfoIn() < for (int i = 0; i < MaxCount; i++) < a_1 = i.ToString(NumberFormatInfo.CurrentInfo); a_2 = i.ToString(NumberFormatInfo.CurrentInfo); a_3 = i.ToString(NumberFormatInfo.CurrentInfo); a_4 = i.ToString(NumberFormatInfo.CurrentInfo); a_5 = i.ToString(NumberFormatInfo.CurrentInfo); a_6 = i.ToString(NumberFormatInfo.CurrentInfo); a_7 = i.ToString(NumberFormatInfo.CurrentInfo); a_8 = i.ToString(NumberFormatInfo.CurrentInfo); a_9 = i.ToString(NumberFormatInfo.CurrentInfo); a_10 = i.ToString(NumberFormatInfo.CurrentInfo); >> [Benchmark(Description = "NumberFormatInfoOut")] public void NumberFormatInfoOut() < NumberFormatInfo culture = NumberFormatInfo.CurrentInfo; for (int i = 0; i < MaxCount; i++) < a_1 = i.ToString(culture); a_2 = i.ToString(culture); a_3 = i.ToString(culture); a_4 = i.ToString(culture); a_5 = i.ToString(culture); a_6 = i.ToString(culture); a_7 = i.ToString(culture); a_8 = i.ToString(culture); a_9 = i.ToString(culture); a_10 = i.ToString(culture); >> [Benchmark(Description = "ToString")] public void DefaultToString() < for (int i = 0; i < MaxCount; i++) < a_1 = i.ToString(); a_2 = i.ToString(); a_3 = i.ToString(); a_4 = i.ToString(); a_5 = i.ToString(); a_6 = i.ToString(); a_7 = i.ToString(); a_8 = i.ToString(); a_9 = i.ToString(); a_10 = i.ToString(); >> >
Далее пошли запуски на разных компьютерах и вот какие получены результаты:
Результаты тестов
Основная суть проблемы производительности при «а напишу как обычно, чего такого?» заключается в мелочах, подсказанных трейсером и кодами от товарищей из Майкрософта.
Если не передавать никаких аргументов, то выполняется неприятное дело (за нас все решают):
[System.Security.SecuritySafeCritical] [Pure] public override String ToString() < Contract.Ensures(Contract.Result() != null); return Number.FormatInt32(m_value, null, NumberFormatInfo.CurrentInfo); >
public static NumberFormatInfo CurrentInfo < get < System.Globalization.CultureInfo culture = System.Threading.Thread.CurrentThread.CurrentCulture; if (!culture.m_isInherited) < NumberFormatInfo info = culture.numInfo; if (info != null) < return info; >> return ((NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo))); > >
В подобной ситуации будут дополнительные накладные расходы на все, на чем только можно и очень хочется избегать подобных ситуаций, ведь оказалось, что это крайне просто – делаем выбор сами, имеем право.
При этом интересна реализация перегрузки:
[Pure] [System.Security.SecuritySafeCritical] public String ToString(IFormatProvider provider) < Contract.Ensures(Contract.Result() != null); return Number.FormatInt32(m_value, null, NumberFormatInfo.GetInstance(provider)); >
И вот тут я нашел то, что лично для меня показалось крайне удачным решением и, откровенно говоря, неявным ускорением о котором вообще не было мыслей:
public static NumberFormatInfo GetInstance(IFormatProvider formatProvider) < // Fast case for a regular CultureInfo NumberFormatInfo info; CultureInfo cultureProvider = formatProvider as CultureInfo; if (cultureProvider != null && !cultureProvider.m_isInherited) < info = cultureProvider.numInfo; if (info != null) < return info; >else < return cultureProvider.NumberFormat; >> // Fast case for an NFI; info = formatProvider as NumberFormatInfo; if (info != null) < return info; >if (formatProvider != null) < info = formatProvider.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo; if (info != null) < return info; >> return CurrentInfo; >
А CultureInfo то проверяется первым, значит, есть вероятность выиграть слегка времени еще и на этом моменте, потому были сделаны те методы, которые были сделаны для тестов.
Стоит обратить внимание
// Сводка: // Возвращает или задает объект System.Globalization.CultureInfo, представляющий // язык и региональные параметры, используемые текущим потоком. // // Возврат: // Объект, представляющий язык и региональные параметры, используемые текущим потоком. // // Исключения: // T:System.ArgumentNullException: // Для свойства задано значение null. public static CultureInfo CurrentCulture
Для себя я сделал вывод, что выгодней всего делать следующим образом:
Результат
const int i = 424242; CultureInfo culture = CultureInfo.CurrentCulture; string str = i.ToString(culture); Console.WriteLine(str);
В чём разница между Convert.ToString(), ToString() и (String)?
Функция ToString() (вы забыли скобки) — это не приведение типов, это просто функция, которая для числа даёт его строковое представление.
Функция Convert.ToString(int) — это тоже не приведение типов, а функция, которая для числа даёт его строковое представление.
Обе функции ToString() и Convert.ToString() выдают строковое представление в текущей локали, так что различий быть не должно.
Приведение типов в ваших примерах лишь одно:
String c = (String)temp;
Это приведение типов не будет работать, т. к. temp имеет реальный тип int , а не string . Это даже не скомпилируется, т. к. компилятор видит, что число типа int никаким образом не может быть строкой. Приведение типов не меняет сам объект, оно просто меняет тип ссылки на настоящий тип объекта или какой-то из его базовых типов. (Вы ведь понимаете, что число 123 — это совсем не то же самое, что строка из трёх символов ‘1’, ‘2’ и ‘3’?)
Уголок зануды пуриста.
Вызовы 5.ToString() и Convert.ToString(5) , согласно sourceof.net, являются вызовами
Number.FormatInt32(5, null, NumberFormatInfo.GetInstance(Thread.CurrentThread.CurrentCulture))
Number.FormatInt32(i, null, NumberFormatInfo.CurrentInfo)
внутреннего класса Number соответственно. Могут ли NumberFormatInfo.GetInstance(Thread.CurrentThread.CurrentCulture) и NumberFormatInfo.CurrentInfo не совпадать?
Код NumberFormatInfo.GetInstance для нашего случая можно упростить до следующего:
if (!cultureInfo.m_isInherited) return cultureInfo.numInfo ?? cultureInfo.NumberFormat; return cultureInfo.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo ?? CurrentInfo;
А код NumberFormatInfo.CurrentInfo — до
CultureInfo cultureInfo = Thread.CurrentThread.CurrentCulture; if (!cultureInfo.m_isInherited && cultureInfo.numInfo != null) return cultureInfo.numInfo; return ((NumberFormatInfo)cultureInfo.GetFormat(typeof(NumberFormatInfo)));
В случае, когда cultureInfo.m_isInherited == false (то есть, у нас CultureInfo , а не производный от него тип), у нас cultureInfo.GetFormat(typeof(NumberFormatInfo)) возвращает NumberFormat , так что различий нет.
В случае же, когда у нас не CultureInfo , а производный от него тип, у нас код упрощается до
cultureInfo.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo ?? CurrentInfo;
((NumberFormatInfo)cultureInfo.GetFormat(typeof(NumberFormatInfo)));
Разный результат может быть лишь в случае, если переопределённая виртуальная функция GetFormat сошла с ума, и возвращает разные типы при разных вызовах.
class WeirdCultureInfo : CultureInfo < public WeirdCultureInfo() : base("en-US") < >bool flag = false; public override object GetFormat(Type formatType) < object result = flag ? (object)DateTimeFormat : NumberFormat; flag = true; return result; >>
Tostring c что это
При выводе строк в консоли с помощью метода Console.WriteLine для встраивания значений в строку мы можем применять форматирование вместо конкатенации:
string name = "Tom"; int age = 23; Console.WriteLine("Имя: Возраст: ", name, age); // консольный вывод // Имя: Tom Возраст: 23
В строке «Имя: Возраст: » на место и затем будут вставляться в порядке следования значения переменныйх name и age
То же самое форматирование в строке мы можем сделать не только в методе Console.WriteLine, но и в любом месте программы с помощью метода string.Format :
string name = "Tom"; int age = 23; string output = string.Format("Имя: Возраст: ", name, age); Console.WriteLine(output);
Метод Format принимает строку с плейсхолдерами типа , и т.д., а также набор аргументов, которые вставляются на место данных плейсхолдеров. В итоге генерируется новая строка.
Спецификаторы форматирования
В методе Format могут использоваться различные спецификаторы и описатели, которые позволяют настроить вывод данных. Рассмотрим основные описатели. Все используемые форматы:
Задает формат денежной единицы, указывает количество десятичных разрядов после запятой
Целочисленный формат, указывает минимальное количество цифр
Экспоненциальное представление числа, указывает количество десятичных разрядов после запятой
Формат дробных чисел с фиксированной точкой, указывает количество десятичных разрядов после запятой
Задает более короткий из двух форматов: F или E
Также задает формат дробных чисел с фиксированной точкой, определяет количество разрядов после запятой
Задает отображения знака процентов рядом с число, указывает количество десятичных разрядов после запятой
Шестнадцатеричный формат числа
Форматирование валюты
Для форматирования валюты используется описатель «C»:
double number = 23.7; string result = string.Format("", number); Console.WriteLine(result); // 24 р. string result2 = string.Format("", number); Console.WriteLine(result2); // 23,70 р.
Число после описателя указывает, сколько чисел будет использоваться после разделителя между целой и дробной частью. При выводе также добавляется обозначение денежного знака для текущей культуры компьютера. В зависимости от локализации текущей операционной системы результат может различаться. Также обратите внимание на округление в первом примере.
Форматирование целых чисел
Для форматирования целочисленных значение применяется описатель «d»:
int number = 23; string result = string.Format("", number); Console.WriteLine(result); // 23 string result2 = string.Format("", number); Console.WriteLine(result2); // 0023
Число после описателя указывает, сколько цифр будет в числовом значении. Если в исходном числе цифр меньше, то к нему добавляются нули.
Форматирование дробных чисел
Для форматирования дробны чисел используется описатель F, число после которого указывает, сколько знаков будет использоваться после разделителя между целой и дробной частью. Если исходное число — целое, то к нему добавляются разделитель и нули.
int number = 23; string result = string.Format("", number); Console.WriteLine(result); // 23,00 double number2 = 45.08; string result2 = string.Format("", number2); Console.WriteLine(result2); // 45,0800 double number3 = 25.07; string result3 = string.Format("", number3); Console.WriteLine(result3); // 25,1
Формат процентов
Описатель «P» задает отображение процентов. Используемый с ним числовой спецификатор указывает, сколько знаков будет после запятой:
decimal number = 0.15345m; Console.WriteLine("", number);// 15,3%
Настраиваемые форматы
Используя знак #, можно настроить формат вывода. Например, нам надо вывести некоторое число в формате телефона +х (ххх)ххх-хх-хх:
long number = 19876543210; string result = string.Format("", number); Console.WriteLine(result); // +1 (987) 654-32-10
Метод ToString
Метод ToString() не только получает строковое описание объекта, но и может осуществлять форматирование. Он поддерживает те же описатели, что используются в методе Format:
long number = 19876543210; Console.WriteLine(number.ToString("+# (###) ###-##-##"));// +1 (987) 654-32-10 double money = 24.8; Console.WriteLine(money.ToString("C2")); // 24,80 р.
Интерполяция строк
Интерполяция строк призвана упростить форматирование строк. Так, перепишем пример с выводом значений переменных в строке:
string name = "Tom"; int age = 23; Console.WriteLine($"Имя: Возраст: "); // консольный вывод // Имя: Tom Возраст: 23
Знак доллара перед строкой указывает, что будет осуществляться интерполяция строк. Внутри строки опять же используются плейсхолдеры <. >, только внутри фигурных скобок уже можно напрямую писать те выражения, которые мы хотим вывести.
Интерполяция по сути представляет более лаконичное форматирование. При этом внутри фигурных скобок мы можем указывать не только свойства, но и различные выражения языка C#:
int x = 8; int y = 7; string result = $" + = "; Console.WriteLine(result); // 8 + 7 = 15
Также внутри фигурных скобок можно выполнять более сложные выражения, например, вызывать методы:
int x = 8; int y = 7; string result = $" * ="; Console.WriteLine(result); // 8 * 7 = 56 int Multiply(int a, int b) => a * b;
Уже внутри строки можно применять форматирование. В этом случае мы можем применять все те же описатели, что и в методе Format . Например, выведем номер телефона в формате +x xxx-xxx-xx-xx:
long number = 19876543210; Console.WriteLine($""); // +1 987 654 32 10
Добавляем пространство до и после форматируемого вывода:
string name = "Tom"; int age = 23; Console.WriteLine($"Имя: Возраст: "); // пробелы после Console.WriteLine($"Имя: Возраст: "); // пробелы до
Имя: Том Возраст: 23 Имя: Том Возраст: 23