Представим, что нам нужно что-нибудь сделать со строками в .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);
}
Что-то в этом коде не так. И проблемы две:
- Медленное выполнение из-за пересоздания строк на каждом шаге благодаря иммутабельности
- Низкоуровневый кусок утилитарного кода, который обычно ссылают в класс с названием
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.
Автор: dokable