Delphi XE3: TStringHelper
Поддержка помощников для простых типов данных является одним из нововведений в последней версии Delphi - XE3. Многие уже написали статьи по этому поводу, но я рассмотрю вопрос немного с другой стороны.
Самая идея расширения простых типов мне нравится, это по-моему очень даже удобно писать, к примеру, str.Length() и т.п. Но давайте посмотрим на класс помощник и его реализацию более пристально. TStringHelper расположен в модуле System.SysUtils, имеет ряд классовых статических методов (class method), и набор обычных. Вроде как подобное расширение призвано повысить, так сказать, объектно-ориентированность языка и простых типов. Т.е позволяет приблизить простой тип к объекту. Конечно минус помощников в том, что он может быть только один, но, мне кажется, в скором будущем эта проблема будет решена. Дак вот, когда мы работаем с объектами у нас тоже есть как классовые методы так и обычные. Вызывая обычный метод, мы предполагаем, что он производит какие-либо действия с самим экземпляром, вызывая же классовый метод, мы можем получить, например, новый экземпляр, либо выполнить действия не отностящиеся к конкретному экземпляру, но по смыслу пренадлежащие классу.
Ну а теперь вернемся к нашему TStringHelper. Private методы нам не интересны, так что их пропустим. В public секции помимо методов и функций объявлена константа Empty, определяющая пустую строку, а также пара свойств Length и Chars[]; Если наличие Length оправдано, и весьма удобно, то целесообразность второго не очевидна. Внимательно присмотревишись к исходному коду, можно заметить, что перед реализацией методов TStringHelper добавлена директива комиплятора {$ZEROBASEDSTRINGS ON}. Поэтому str.Chars[i] не эквивалентно записи str[i] (как можно увидеть в исходном коде, где написано result := self[i]). Фактически str.Chars[0] возвращает str[1].
Перейдем ко классовым методам. Поскольку эти методы не преднозначены для вызова с использованием экземпляра (никто не мешает, конечно, это сделать, но логически цель иная), то и не будем использовать код а-ля str.Create() для них. Здесь становится непривычным, как выглядит вызов методов. Ибо обычно все таки идет название класса, а потом метод, а тут приходится писать просто string.Create, и необычно это именно тем, что само по себе слово string никогда не встречалось нам ранее в выполняемом коде программы. Сам метод Create имеет три перегруженных варианта. Первый из них фомирует строку из символа, повторяя его указанное число раз. Третий преобразует массив символов в строку. Здесь следует учесть, что массив можно передать любым образом: это может быть статический массив array[1..10] of char (этот вариант впрочем приводится к строке неявно), либо открытый - array of char, либо возможна передача Create(['a','b']), но сомневаюсь, что кто то вызовет его таким вот образом. Третий же вариант Create также получает массив, но имеет два дополнительных парметра - стартовый индекс и число символов из массива. В общем и целом вызовы методов сводятся просто к переадресации вызова функции, например, для первой версии построения строки из символов происходит переадресация к методу StringOfChar модуля System.
Следующим блоком классовых методов являются функции Compare. Доступны 4 вариации, которые могут быть полезны. Только вот не ясно в чем прелесть написания string.Compare(strA, strB), если можно написать strA.CompareTo(strB). Второй способ написания даже выглядит более естественно: СтрокуА.СравнитьСо(строкойБ), нежели Строки. Сравнить(строкуА и СтрокуБ). Т.е. цель введения последнего не ясна, с таким же успехом можно воспользоваться одним из обычных функций сравнения строк, коих предостаточно. Т.е. возникает впечатление, что некоторые функции реализованы для совсем ленивых, не способных прочитать справку для SysUtils и StrUtils.
Следующие методы более интересны, я даже залез в справку, почитать документацию по их описанию. Кстати, в справке есть ошибка: в списке класса-помощника перечислены методы и события TObject, что не имеет отношения к действительности.
Дак вот, следующим по алфавиту является мифический перегруженный классовый метод CompareOrdinal. Мифическим этот метод я назвал по одной простой причине: он не реализован (в обеих версиях).
class function TStringHelper.CompareOrdinal(const strA, strB: string): Integer; begin Result := -1; end;
На самом деле это стало для меня сюрпризом, хотя и обнаружил я такие моменты не на этом методе, а скорей обнаружение одного из методов "заглушек" стало поводом для написания данной заметки.
Следующее на очереди - копирование. Метод копирования - классовый. Предполагается писать strA := string.Copy(strB). зачем здесь классовый метод? почему не просто strA := strB.Copy(). Объясните мне кто-нибудь, в чем в данном случае логика создания метода классовым? Второй вариант в очередной раз (на мой, конечно, взгяд) удобнее.
Аналогичная ситуация возникает с методами EndsText, и пергруженным EndsWith. Зачем использовать string.EndsText(txt, subtxt), если доступен вариант txt.EndsWith(subtxt) ? Кто-то будет использовать первый вариант? сомневаюсь. Далее то же самое касается string.equals(a,b) против a.equals(b).
Метод Format() просто перенапраляет во всем известный Format(). Почему то объявлен как overload, но доступен только один вариант. Стоило объявить как inline, хотя возможно имеет место быть магия компилятора.
В общем, наверное, не имеет смысла расписывать все методы попощника. Но вкраце ситуация следующая:
- GetHashCode - загулушка, возвращет -1
- Код в IsEmpty кажется писал не тот человек, который писал Equals. В противном случае я не могу объяснить разницу в стиле кода:
function TStringHelper.IsEmpty: Boolean; begin if Self = Empty then Result := True else Result := False; end;
против использованной ранее записи result := (self = equalString); Метод IsNullOrEmpty, кстати имеет идентичный код. - Метод IsNullOrWhiteSpace является заглушкой, всегда возвращает false. С этого метода я и обратил более пристальное внимание на класс помощник.
- Два варианта Join, (одна принимающая параметр IEnumerable<string>, и одна принмиающая массив констант? (array of const)) - заглушки
- Вообще будьте бдительны. TStringHelper, как я уже говорил, скомплирован с директивой {$ZEROBASEDSTRINGS ON}. Незнание этого чревато ошибками. Например, интуитивно понятный метод 'asd'.indexOf('a',1) вернет результат -1. Вторым параметром метода является стартовая позиция поиска (т.е. найти индекс позиции символа а в строке asd, начиная с позиции 1). Т.е. в обычном понимании, когда строки индексируются с 1, результат должен быть равен 1. А фактически такого не происходит. Хотя с другой стороны, это можно считать ошибкой разработчика (скорее так и есть).
Резюмируя все вышенаписанное, хочется сказать, что функционал помощников сам по себе очень удобен. Минусы в невозможности создания нескольких помощников. Часть функций включенных в TStringHelper действительно очень востребована и удобна (length, contains, ends, и многие другие), их я буду с удовольствием использовать. Другая же (бОльшая) часть врядли будет когда-либо кем-то использована. Отствутсвие реализации тела некоторых функций вызывает некоторое недоумение для финального релиза продукта. А {$ZEROBASEDSTRINGS ON} может внести путаницу (а было ли оно вообще востребовано, кажется только усложнили код, и могли наплодить ошибок?). Поэтому перед началом использования каких-либо методов, получающих параметры-индексы лучше два раза перепроварить, как они работают (а потом когда выйдет апдейт с исправлением, исправить свой код заново (: ). Пословица "Доверяй, да проверяй" становится весьма актульной в этом контексте. Еще одним из недостатков можно считать следующее: мы получили класс помощник для string (UnicodeString), но у нас есть ведь и AnsiString и WideString, и они "за бортом". Будет странным, что код:
s1 := '123'; s2 := s1; writeln(s1.Length); writeln(s2.Length);
закончится ошибкой компиляции, из-за того, что использованные строки имеют разный тип.
12.10.2012 в 12:16
Появление хэлпера для строк - это первый шаг к переходу с 1-основных строк на 0-основные.
Скорее всего в ближайшее время мы узнаем причину появления хэлперов для юникод-строк.
12.10.2012 в 13:07
будем ждать развития событий (:
12.10.2012 в 13:05
А новые как раз позволяют свести работу с любым типом строк к работе с 0-основным. И лучше свои новые работы переводить на использование этого хэлпера.
12.10.2012 в 16:39
Андрей, добавь на сайт чего-нибудь для удобного репорта опечаток.
15.10.2012 в 16:08
20.07.2015 в 17:51