Работа со строками в .NET

 

Хотя работа со строками довольно хорошо описана в книге Дж. Рихтера, я решил затронуть эту тему, т.к. не у всех есть возможность прочитать эту книгу, а неправильное использование строк является частой ошибкой и ведет к снижению производительности приложения. К тому же, в книге имеется одна небольшая неточность по работе класса 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 пока завершен. Я многого не рассказал, но для первого знакомства этого вполне достаточно.

Permalink | Комментарии (2) | Post RSSRSS comment feed

Собеседование в EPAM (Киевский офис)

В контексте предыдущей новости решил разместить пост со старого блога - может кому-то пригодится.

Знакомый ходил в марте этого (2006) года на собеседование в EPAM. Далее привожу всю имеющуюся у меня информацию и рассказ от его имени.

Официальный сайт: http://www.epam.com/
Белорусская компания с офисами в Белоруссии, России и Венгрии.
Киевский офис открылся в конце 2005 года. Если верить пресс-релизу, до конца 2006 года планируют набрать 200 человек.

Офис находится в центре, возле метро Республиканский Стадион, в новом офисном центре (на одном этаже с Циклумом, кто в курсе). На первый взляд тесновато, но HR менеджер сказала, что уже ищут новый офис.

HR менеджер задавала общие вопросы (почему хотите сменить работу, что нравится на текущей работе и т.п.), провела короткий тест на знание разговорного английского (беседа на английском на левую тему). Затем, запустила тест по C# на Brainbench.

После этого было интервью с Project Manager. Вопросов по знанию технологий небыло. Попросил рассказать про организацию процесса разработки какого-либо проекта, в котором принимал участие.
Затем, ответил на интересующие вопросы. Сказал, что по тесту Джоэла фирма набирает 11 баллов, т.к. еще не организовано коридорное тестирование. Но код меня никто писать не просил. Так что уже получается не больше 10-ти. По CMMI - уровень 4.

Оплачиваемые отпуск и больничные, ограничений на ICQ и интернет нет. Белая зарплата. Приходить можно с 9 до 11. Отработать в неделю необходимо 40 часов.

В целом, о фирме сложилось положительное впечатление. Хотя ничего особо выдающегося нет, на фоне многих киевских фирм EPAM выглядит очень хорошо.

Permalink | Комментарии (0) | Post RSSRSS comment feed

Принцесса или тигр?

Еще в детстве мне попалась замечательная книга головоломок Рэймонда М. Смаллиана "ПРИНЦЕССА ИЛИ ТИГР?". Некоторые задачи я хотел бы представить вашему вниманию. Название книга получила по одной из своих глав. Для я привожу первые три задачи из этой главы. Они очень легкие и, я надеюсь, не займут у вас много времени.

Принцесса или тигр?

   У Фрэнка Стоктона есть сказка, которая называется "Принцесса или тигр?" В этой сказке один узник должен угадать, в какой из двух комнат находится принцесса, а в какой - тигр. Если он укажет на первую комнату, то женится на принцессе, если на вторую, то его (вполне возможно) растерзает тигр.

   В некотором царстве правил король. Однажды он тоже прочитал эту сказку.
-  В самый раз для моих заключенных! - сказал он своему министру. - Только я не хочу полагаться на случайности. Пусть на дверях каждой комнаты повесят по табличке, а заключенному будет кое-что сказано о них. Если узник не дурак и способен рассуждать логически, он сумеет сохранить себе жизнь и в придачу заполучить прелестную невесту.
-  Блестящая идея, ваше величество! - согласился министр.

Испытания первого дня

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

1. Первое испытание.

- А что, если в обеих комнатах сидят тигры? - спросил узник. - Что же мне тогда-то делать?
- Считай, не повезло, - ответил король.
- А если в обеих комнатах окажется по красавице? - поинтересовался узник.
- Считай, подфартило, - сказал король. - Уж это ты и сам бы мог сообразить!
- Ну, хорошо, а если в одной комнате принцесса, а в другую посадили тигра, что тогда? - не успокаивался узник.
- Вот тут-то уже все зависит от тебя! Не так ли?
- Да откуда же мне знать, где кто? - сокрушенно вздохнул узник.
   Тут король указал на таблички, прикрепленные к дверям каждой из комнат. На них было написано:

I
В этой комнате находится принцесса, а в другой комнате сидит тигр

II
В одной из этих комнат находится принцесса; кроме того, в одной из этих комнат сидит тигр

- А это правда, что здесь написано? - спросил узник.
- На одной - правда, - отвечал король, - на другой - нет.
   А вы на месте узника, какую бы дверь открыли? (Конечно, если вы предпочитаете принцессу тигру.)

2. Второе испытание.

   Итак, первый узник спас себе жизнь и на радостях отбыл вместе с принцессой.
   Таблички на дверях сменили, соответственно были подобраны и обитатели комнат. На этот раз на табличках можно было прочитать следующее:

I
По крайней мере в одной из этих комнат находится принцесса
II
Тигр сидит в другой комнате

- Истинны ли утверждения на табличках? -  спросил второй узник.
- Может, оба истинны, а может, оба ложны, -   ответил ему король.
   Какую из комнат следует выбрать второму узнику?

3. Третье испытание.

   Во время этого испытания король объявил, что опять утверждения на обеих табличках одновременно либо истинны, либо ложны. Надписи же были вот какие:

I
Либо в этой комнате сидит тигр, либо принцесса находится в другой комнате
II
Принцесса в другой комнате

   Кто же обнаружится в первой комнате - принцесса или тигр? А во второй?

 

Ответы смотрите в комментариях.

Permalink | Комментарии (3) | Post RSSRSS comment feed

Вызывать ли Dispose у DataSet

Надеюсь, что все знают, зачем предназначен интерфейс IDisposable и для чего надо вызывать метод Dispose этого интерфейса. Однако, хочу очень кратко напомнить, на случай, если кто-то забыл (если кто-то слышит про Dispose впервые, то советую почитать соответствующую литературу).

Итак, если .NET класс не является оболочкой для неуправляемого ресурса (например, connection к базе данных, handle файла и т.п.), то программисту не надо заботиться о его уничтожении. Для этого предназначен сборщик мусора. Если же класс использует неуправляемые ресурсы, то необходим механизм их освобождения. Для этого предназначен интерфейс IDisposable и его метод Dispose. Этот метод следует вызывать для освобождение неуправляемых ресурсов. Кроме того, как правило, переопределяется метод Finalize (для C# это делается созданием метода с синтаксисом деструктора), в котором, также, происходит освобождения неуправляемых ресурсов на случай, если явно вызвать Dispose забыли. Явный вызов Dispose намного предпочтительнее, так как, во-первых, Finalize вызывается сборщиком мусора, который может начать работу через довольно длительное время, во-вторых, объекты, для которых вызывается Finalize, помещаются в специальную очередь завершения и существуют до следующей сборки мусора (т.е. занимают память дополнительное время). В методе Dispose, кроме освобождения неуправляемых ресурсов, вызывается GC.SuppressFinalize(), который сообщает сборщику мусора, что вызывать Finalize для этого объекта не надо.

Весь этот механизм очень подробно описан в книге Джефри Рихтера.

В принципе, если класс имплементирует IDisposable, то надо вызывать Dispose. Это однозначно для таких классов, как SqlConnection и SqlDataReader. Однако, насчет DataSet часто возникают вопросы.

Итак, DataSet имплементирует интерфейс IDisposable. Следовательно, правильно было бы вызывать метод Dispose при завершении работы с датасетом.

Для чего нужен Dispose? Для освобождения неуправляемых ресурсов. Память, занимаемая объектом, освобождается сборщиком мусора независимо от вызова Dispose. А какие неуправляемые ресурсы использует датасет? Никаких. Дело в том, что имплементация IDisposable достается ему в наследство от MarshalByValueComponent.

Что же делает Dispose в случае датасета? Вызывает GC.SuppressFinalize(), который сообщает сборщику мусора, что метод Finalize вызывать не надо. Больше ничего.

Значит, если мы не вызовем Dispose, ничего плохого не произойдет, т.к. никаких неуправляемых ресурсов освобождать не надо? Не совсем так. Дело в том, что тогда сборщик мусора вызовет метод Finalize и переместит объект в очередь завершения, что продлит время жизни объекта до следующей сборки мусора.

Получается, что сторонники вызова Dispose правы. Но давайте рассмотрим DataTable. Он, тоже, наследуется от MarshalByValueComponent. Если следовать нашей логике, необходимо вызывать Dispose и для каждого DataTable в DataSet. Я не знаю никого, кто так делает. А вы? Вызывать Dispose для DataSet и не вызывать для DataTable, как минимум, нелогично, т.к. объектов DataTable больше.

Что же делать? Вызывать или не вызывать? Не вызывать! Дело в том что в конструкторах DataSet и DataTable вызывается GC.SuppressFinalize() и, следовательно, сборщик мусора не будет вызывать Finalize!

Permalink | Комментарии (1) | Post RSSRSS comment feed

Почему Round &quot;раундит&quot; &quot;не правильно&quot; или все про округление в .NET

Как Вы думаете, какой результат получится в результате выполнения этого кода:

Console.WriteLine(Math.Round(3.5));
Console.WriteLine(Math.Round(4.5));

Если Вы думаете, что получится 4 и 5, то ошибаетесь. В обоих случаях результатом будет 4. Тот же самый результат Вы получите и в Visual Basic 6.0. В .NET статический метод Round() класса Math округляет половину к ближайшему четному. В школе же нас учили, что половина всегда округляется в большую сторону. Поэтому многие (и я в том числе) очень удивляются, узнав о таком "неправильном" округлении. Часто незнание этого факта может привести к неправильным расчетам, если алгоритмы преполагают "обычное" округление.

Что же это такое? Очередной баг Microsoft? Вовсе нет! Просто существует несколько способов округления.

Округление в меньшую сторону

Простейший случай - когда цифры после заданной точности просто отбрасываются (округляем до целого):

3.9 округляется до 3
-3.9 округляется до -3

Это, так называемое, симметричное округление, когда число округляется только по абсолютной величине, без учета знака.
В .NET симметричное округление в меньшую сторону производится простым привидением к целому:

Console.WriteLine((int)3.9); // 3
Console.WriteLine((int)-3.9); // 3

С учетом знака -3.9 округляется до -4. Метод Floor() класса Math производит несимметричное округление:

Console.WriteLine(Math.Floor(3.9)); // 3
Console.WriteLine(Math.Floor(-3.9)); // -4

Округление в большую сторону

Несимметричное округление до ближайшего большего или равного целого выполняет метод Ceiling() класса Math:

Console.WriteLine(Math.Ceiling(3.1));
Console.WriteLine(Math.Ceiling(-3.1));

Заметьте, что Floor() и Ceiling() округляют всегда до целого.

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

Арифметическое округление

Это привычное нам округление, когда половина округляется в большую сторону:

3.5 округляется до 4
4.5 округляется до 5

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

Банковское округление

Если складывать много чисел, округляя .5 всегда в большую сторону, то возникнет перекос, который будет тем больше, чем больше чисел мы складываем. Банковское округление позволяет минимизировать этот перекос. В этом случае половина округляется к ближайшему четному. Метод Round() класса Math реализует именно банковское округление. В качестве параметра он принимает округляемое значение и, возможно, точность, до которой необходимо выполнить округление. Если точность не указана, то округление выполняется до целого.

Случайное округление

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

Попеременное (alternate) округление

В этом случае .5 округляется попеременно то в большую сторону, то в меньшую.

 

Что же делать, если надо произвести арифметическое округление? К сожалению, Microsoft не реализовала соответствующий метод в классе Math (так же, как и для случайного и попеременного округлений). Но выход, конечно, есть.

Во первых, это класс SqlDecimal из пространства имен System.Data.SqlTypes со статическим методом Round:

Console.WriteLine(SqlDecimal.Round(3.5m, 0)); // 4
Console.WriteLine(SqlDecimal.Round(4.5m, 0)); // 5

Это отличие связано с тем, что Round из Sql Server выполняет математическое округление.

Во вторых, можно самостоятельно написать метод для математического (и не только) округления. Например, вот реализация на C# для симметричного арифметического округления.

public static double Round(double value, int digits)
{
  double scale = Math.Pow(10.0, digits);
  double round = Math.Floor(Math.Abs(value) * scale + 0.5);
  return (Math.Sign(value) * round / scale);
}

Я предпочитаю поместить несколько перегрузок приведенной реализации (для float, double, decimal) в некоторый служебный класс и использовать их вместо Math.Round() (мне пока не приходилось реализовывать задачи, требующие банковского округления).

Реализацию описанных алгоритмов округления на Visual Basic 6.0 можно найти здесь: http://support.microsoft.com/default.aspx?scid=kb;en-us;196652

Добавлено 25.10.2005

В .NET 2.0 метод Round() класса Math имеет перегрузки с параметром mode типа MidpointRounding, определяющим, как будет округляться половина. MidpointRounding может принимать два значения:

  • AwayFromZero - половина округляется к ближайшему числу, которое дальше от нуля (т.е обыкновенное математическое симметричное округление).
  • ToEven - округление половины к ближайшему четному - единственная текущая реализация Round(). Естественно, если параметр mode не задан, по умолчанию используется ToEven (для совместимости).

Permalink | Комментарии (0) | Post RSSRSS comment feed

Старые материалы

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

Permalink | Комментарии (0) | Post RSSRSS comment feed
Реклама
TNX.net - уникальный международный сервис для вебмастеров и оптимизаторов

Подписка
toodoo Читать в Яндекс.Ленте Добавить в Google Reader или Homepage

Статистика
]]>
  • PR0CY.com - сервис проверки доменов
  • BlogMemes.ru
]]>





]]>

]]>