Элегантные строки

от автора

Представим, что нам нужно что-нибудь сделать со строками в .net. Что-то не очень сложное, но и не совсем простое. Например, для правильного форматирования, расставить пробелы после запятых в тексте. Что же предлагает .net из коробки?
Что-то такое:

string str = "..."; str.Replace(",", ", "); 

Постойте, но мы же хотели расставлять пробелы, а не заменять запятые!..

Хорошо, пойдем дальше.
Давайте, введем цензуру. Не будем разрешать в наших текстах, скажем, слово «медведь». Вот так вот запросто. Будем подменять каждого «медведя» многоточием.
Ага, подменять. Значит логично использовать все тот же метод Replace. Сказано — сделано:

string str = "Лев и медведь добыли мясо и стали за него драться. Медведь не хотел уступить, и лев не уступал."; var result = str.Replace("медведь", "..."); 

Хм, многоточие вместо первого медведя появилось, а вот второй был слишком горд и начинался с большой буквы. И наш метод перед ним спасовал. Придется пробежаться второй раз и поменять еще и гордых «Медведей».

string str = "Лев и медведь добыли мясо и стали за него драться. Медведь не хотел уступить, и лев не уступал."; var result = str.Replace("медведь", "...").Replace("Медведь", "..."); 

Фух, получилось. Не очень красиво, но работает. Но работает ли? Вдруг придет такой «меДВедь»?
Мы подумали, напряглись и отсекли таких наглецов тоже. Но какой ценой!

string str = "Лев и медведь добыли мясо и стали за него драться. Медведь не хотел уступить, и лев не уступал."; int index = str.IndexOf("медведь", StringComparison.CurrentCultureIgnoreCase); while (index >= 0) {     str = str.Remove(index, "медведь".Length);     str = str.Insert(index, "...");     index = str.IndexOf("медведь", StringComparison.CurrentCultureIgnoreCase); } 

Что-то в этом коде не так. И проблемы две:

  1. Медленное выполнение из-за пересоздания строк на каждом шаге благодаря иммутабельности
  2. Низкоуровневый кусок утилитарного кода, который обычно ссылают в класс с названием Util и забывают, посреди прелестного семантично-выверенного проекта (ну, хотя бы фантазиях же можно?)

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

Согласитесь, существующий интерфейс работы со строками в .net морально устарел. Он архаичен, недостаточно гибок и заставляет писать много странного кода для, казалось бы, обычных и простых операций раз за разом.
Еще не забудьте проверки на null. Проверки граничных значений индекса. И извольте правильно обойтись с длинами строк.

Так родилась идея Fluent интерфейса библиотеки для работы со строками.
Современного, читабельного, и так же хорошо протестированного.

Посмотрим, что же из этого получилось.

Пример операции вставки:

string t = "Строка будет вставлена после второго слова маркер. Я тот самый маркер! А этот маркер будет проигнорирован"            .Insert(", а тут был Вася").After(2, "Маркер").IgnoringCase().From(The.Beginning); t.Should().Be("Строка будет вставлена после второго слова маркер. Я тот самый маркер, а тут был Вася! А этот маркер будет проигнорирован"); 

Читается как Insert " а тут был Вася" after second "маркер" ignoring case from the beginning. Хотя, что это я? И так же все понятно.

Что-нибудь удалим:

string t = "Эта строчка будет удалена ->ТЕСТ и эта тоже ->ТЕСТ, а эта останется ->ТЕСТ"            .Remove(2, "ТЕСТ"); transformed.Should().Be("Эта строчка будет удалена -> и эта тоже ->, а эта останется ->ТЕСТ"); 

А теперь удалим все, учитывая регистр:

string t = "Строка ТЕСТ будет удалена с обоих концов ТЕСТ".RemoveAll("тЕСт").IgnoringCase(); t.Should().Be("Строка  будет удалена с обоих концов "); 

Или даже так:

string t = "Some very long string".RemoveChars('e', 'L', 'G').IgnoringCase(); t.Should().Be("Som vry on strin"); 

И так:

string t = "Очень длинная строка с русскими буквами, ё".RemoveVowels().For("ru"); t.Should().Be("чнь длнн стрк с рсскм бквм, "); 

Нашлось место и для расширения стандартной логики:

bool isEmptyOrWhiteSpace = "  ".IsEmpty().OrWhiteSpace(); isEmptyOrWhiteSpace.Should().Be(true); 

Проход туда:

var indexes = "Текст в котором есть маркер, потом еще один маркер и напоследок МАРКЕР большими буквами"               .IndexesOf("МаРкЕр").IgnoringCase(); indexes.Should().ContainInOrder(21, 44, 64); 

И обратно:

var indexes = "Текст в котором есть маркер, потом еще один маркер и напоследок МАРКЕР большими буквами"               .IndexesOf("маркер").From(The.End); indexes.Should().ContainInOrder(44, 21); 

А пример с медведями получается компактным и легко читаемым:

string str = "Лев и медведь добыли мясо и стали за него драться. Медведь не хотел уступить, и лев не уступал."; var result = str.ReplaceAll("медведь").With("...").IgnoringCase(); 

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

Быстро попробовать можно при помощи NuGet.
А помочь проекту на GitHub или CodePlex.

ссылка на оригинал статьи http://habrahabr.ru/post/176809/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *