Pull to refresh

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

Reading time 4 min
Views 16K
Представим, что нам нужно что-нибудь сделать со строками в .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.
Tags:
Hubs:
+7
Comments 41
Comments Comments 41

Articles