Хотя работа со строками довольно хорошо описана в книге Дж. Рихтера, я решил затронуть эту тему, т.к. не у всех есть возможность прочитать эту книгу, а неправильное использование строк является частой ошибкой и ведет к снижению производительности приложения. К тому же, в книге имеется одна небольшая неточность по работе класса StringBuilder.
В .NET все строки являются экземплярами класса System.String (алиас string в C#). Он неявно наследуется от System.Object и реализует интерфейсы IComparable, ICloneable, IConvertible, IEnumerable:
[Serializable] |
public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable |
C# рассматривает String как примитивный тип, поэтому для инициализации строковой переменной литералом следует использовать такой синтаксис:
String s = “This is a string.“; |
Компилятор размещает строковые литералы в метаданных модуля, а доступ к ним в run-time происходит с использованием так называемого механизма интернирования строк (string interning). Об интернировании я расскажу немного позже.
Так работать не будет:
String s = new String(“This is a string.“); |
Т.к. String не имеет конструктора, принимающего String. Конструкторы String позволяют создать экземпляр из символа (char) или массива символов (char[]). При вызове из C++ with Managed Extensions или небезопасного кода C# (использующего указатели) применяются конструкторы, принимающие char* или sbyte*.
C# использует синтаксис С++ для задания специальных символов типа перевода строки или табуляции:
String s = “Line\r\nNew Line“; |
При выводе, например, с помощью Console.Write(s), “New Line” будет выведено на новой строке.
“\r\n” соответствует константе vbCrLf в Visual Basic 6.0. Но существует более правильный способ перевода строки. Класс System.Environment содержит read-only свойство NewLine, возвращающее соответствующую строку, в зависимости от платформы.
String s = “Line” + Environment.NewLine + “New Line“; |
Например, на платформе UNIX Environment.NewLine вернет \n.
String s = “Column1\tColumn2“; |
Column1 и Column2 будут разделены табуляцией.
Символ обратной косой черты (“\”) является служебным и при использовании внутри строки его необходимо удваивать:
String path = “c:\\Program Files\\Microsoft Visual Studio 2003\\“; |
Однако, при помощи символа @ (коммерческое эт) можно определить так называемые “дословные строки” (verbatim strings). В этом случае компилятор воспринимает обратную косую черту как обычный символ. Этот код эквивалентен предыдущему:
String path = @”c:\Program Files\Microsoft Visual Studio 2003\“; |
Важнейшим свойством строк в .NET является их неизменяемость (immutable). Это означает что созданную строку невозможно изменить. Следствием этого является то, что любой метод класса String не изменяет его. Например:
string s = “Some String“; Console.WriteLine(s.ToUpper()); |
В этом примере создается строковый объект s, у которого вызывается метод ToUpper(). Это приводит к созданию нового строкового объекта (“SOME STRING”), который выводится на консоль. При этом s не изменилось. Часто, для достижения результата, приходится вызывать последовательно несколько методов класса String:
string newString = s.Trim().Substring(10, 20).ToLower();
Этот код создает 3 новых строки, две из которых сразу становятся мусором. Не стоит особо беспокоиться об этом, т.к. такие кратковременно живущие объекты легко уничтожаются сборщиком мусора.
Однако, если вам необходимо часто формировать строки в своем приложении, то множество промежуточных строковых объектов все же может сказаться на производительности.
Для этого в .NET Framework имеется специальный класс StringBuilder. Он позволяет модифицировать содержащуюся в нем строку без потери производительности (не создавая промежуточных объектов).
Для этого используются следующме методы класса: Append, AppendFormat, Insert, Remove, Replace.
Как же работает класс StringBuilder? Джеффри Рихтер в своей книге “Программирование на платформе .NET FRAMEWORK” пишет:
У объекта StringBuilder предусмотрено поле с ссылкой на массив структур Char. Используя члены StringBuilder, вы можете эффективно манипулировать этим массивом, сокращая строку и изменяя символы строки. При увеличении строки, представляющей ранее выделенный массив символов, StringBuilder автоматически выделит память для нового, большего по размеру массива, скопирует символы и приступит к работе с новым массивом. Прежний массив станет мусором.
Возможно, что в бета версии .NET, по которой Рихтер писал свою книгу, так и было. Но давайте заглянем сами в класс StringBuilder (с помощью утилиты .NET Reflector). Класс содержит следующее объявление:
internal string m_StringValue; |
Как вы помните, модификатор доступа internal (внутренний) эквивалентен public для классов той же сборки и private для классов других сборок.
Далее, методы, модифицирующие строку, вызывают внутренние небезопасные (internal unsafe) методы класса String, которые манипулируют строкой напрямую:
internal unsafe void ReplaceCharInPlace(char oldChar, char newChar, int startIndex, int count, int currentLength) |
{ |
int num1 = startIndex + count; |
fixed (char* local1 = &this.m_firstChar) |
{ |
for (int num2 = startIndex; num2 < num1; num2++) |
{ |
if (local1[num2] == oldChar) |
{ |
local1[num2] = newChar; |
} |
} |
} |
} |
Благодаря модификатору доступа internal эти методы могут вызывать только классы сборки mscorlib, что исключает проблемы, присущие неуправляемым приложениям.
В заключение, хотелось бы рассказать немного об очень полезном методе Format класса String. Он, также, как и метод AppendFormat класса StringBuilder позволяет эффективно сформировать строку из строки со спецификаторами формата и строковых представлениях объектов. На самом деле, в методе Format создается объект StringBuilder и вызывается его метод AppendFormat. Это позволяет вам сэкономить несколько строк кода.
Вот одна из наиболее часто используемых перегрузок метода Format:
public static string Format( string format, params object[] args ); |
Вот как правильнее написать пример с переводом строки:
String s = String.Format(“Line{0}New Line“, Environment.NewLine); |
Вот еще один пример:
int x = 10; int y = 20; String s = String.Format(“{0} + {1} = {2}“, x, y, x + y); |
Если вы не знакомы со спецификаторами формата, обязательно прочтите о них в MSDN. Это очень удобное и эффективное средство для форматирования строк.
На этом краткий обзор строк в .NET пока завершен. Я многого не рассказал, но для первого знакомства этого вполне достаточно.