Хотя низкоуровневые детали языка CIL пока подробно не рассматривались, обратите внимание на многочисленные
вызовы кода операции
ldstr
("load string" — "загрузить строку"). Попросту говоря, код операции
ldstr
языка CIL загружает новый объект
string
в управляемую кучу. Предыдущий объект
string
, который содержал значение
"Му other string"
, будет со временем удален сборщиком мусора.
Так что же в точности из всего этого следует? Выражаясь кратко, класс
string
может стать неэффективным и при неправильном употреблении приводить к "разбуханию" кода, особенно при выполнении конкатенации строк или при работе с большими объемами текстовых данных. Но если необходимо представлять элементарные символьные данные, такие как номер карточки социального страхования, имя и фамилия или простые фрагменты текста, используемые внутри приложения, тогда тип
string
будет идеальным вариантом.
Однако когда строится приложение, в котором текстовые данные будут часто изменяться (подобное текстовому процессору), то представление обрабатываемых текстовых данных с применением объектов
string
будет неудачным решением, т.к. оно практически наверняка (и часто косвенно) приведет к созданию излишних копий строковых данных. Тогда каким образом должен поступить программист? Ответ на этот вопрос вы найдете ниже.
Использование типа System.Text.StringBuilder
С учетом того, что тип
string
может оказаться неэффективным при необдуманном использовании, библиотеки базовых классов .NET Core предоставляют пространство имен
System.Text
. Внутри этого (относительно небольшого) пространства имен находится класс
StringBuilder
. Как и
System.String
, класс
StringBuilder
определяет методы, которые позволяют, например, заменять или форматировать сегменты. Для применения класса
StringBuilder
в файлах кода C# первым делом понадобится импортировать следующее пространство имен в файл кода (что в случае нового проекта Visual Studio уже должно быть сделано):
// Здесь определен класс StringBuilder:
using System.Text;
Уникальность класса
StringBuilder
в том, что при вызове его членов производится прямое изменение внутренних символьных данных объекта (делая его более эффективным) без получения копии данных в модифицированном формате. При создании экземпляра
StringBuilder
начальные значения объекта могут быть заданы через один из множества конструкторов. Если вы не знакомы с понятием конструктора, тогда пока достаточно знать только то, что конструкторы позволяют создавать объект с начальным состоянием при использовании ключевого слова
new
. Взгляните на следующий пример применения
StringBuilder
:
static void FunWithStringBuilder
{
Console.WriteLine("=> Using the StringBuilder:");
StringBuilder sb = new StringBuilder("**** Fantastic Games ****");
sb.Append("\n");
sb.AppendLine("Half Life");
sb.AppendLine("Morrowind");
sb.AppendLine("Deus Ex" + "2");
sb.AppendLine("System Shock");
Console.WriteLine(sb.ToString);
sb.Replace("2", " Invisible War");
Console.WriteLine(sb.ToString);
Console.WriteLine("sb has {0} chars.", sb.Length);
Console.WriteLine;
}
Здесь
создается объект
StringBuilder
с начальным значением
"**** Fantastic Games ****"
. Как видите, можно добавлять строки в конец внутреннего буфера, а также заменять или удалять любые символы. По умолчанию
StringBuilder
способен хранить строку только длиной 16 символов или меньше (но при необходимости будет автоматически расширяться): однако стандартное начальное значение длины можно изменить посредством дополнительного аргумента конструктора:
// Создать экземпляр StringBuilder с исходным размером в 256 символов.
StringBuilder sb = new StringBuilder("**** Fantastic Games ****", 256);
При добавлении большего количества символов, чем в указанном лимите, объект
StringBuilder
скопирует свои данные в новый экземпляр и увеличит размер буфера на заданный лимит.
Сужающие и расширяющие преобразования типов данных
Теперь, когда вы понимаете, как работать с внутренними типами данных С#, давайте рассмотрим связанную тему преобразования типов данных. Создайте новый проект консольного приложения по имени
TypeConversions
и добавьте его в свое решение. Приведите код к следующему виду:
using System;
Console.WriteLine("***** Fun with type conversions *****");
// Сложить две переменные типа short и вывести результат.
short numb1 = 9, numb2 = 10;
Console.WriteLine("{0} + {1} = {2}",
numb1, numb2, Add(numb1, numb2));
Console.ReadLine;
static int Add(int x, int y)
{
return x + y;
}
Легко заметить, что метод
Add
ожидает передачи двух параметров
int
. Тем не менее, в вызывающем коде ему на самом деле передаются две переменные типа
short
. Хотя это может выглядеть похожим на несоответствие типов данных, программа компилируется и выполняется без ошибок, возвращая ожидаемый результат 19.
Причина, по которой компилятор считает такой код синтаксически корректным, связана с тем, что потеря данных в нем невозможна. Из-за того, что максимальное значение для типа
short
(32 767) гораздо меньше максимального значения для типа
int
(2 147 483 647), компилятор неявно расширяет каждое значение
short
до типа
int
. Формально термин расширение используется для определения неявного восходящего приведения которое не вызывает потерю данных.