Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_5713.html
 

Delphi XE3: TStringHelper

Опубликовано 11.10.2012 г. 23:56

Поддержка помощников для простых типов данных является одним из нововведений в последней версии 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);

закончится ошибкой компиляции, из-за того, что использованные строки имеют разный тип.

 

Метки:  delphi 

Комментарии

Ярослав Бровин
12.10.2012 в 12:16
Статья хорошая. Мне понравилась.

Появление хэлпера для строк - это первый шаг к переходу с 1-основных строк на 0-основные.

Скорее всего в ближайшее время мы узнаем причину появления хэлперов для юникод-строк.
teran
12.10.2012 в 13:07
что-то мне подсказывает что переход на 0-индексированные строки вызовет хаос в совместимости. Пусть не со стартвовым индексом строки дак с последним, придется юзать high(str) вместо length(str).

будем ждать развития событий (:
Ярослав Бровин
12.10.2012 в 13:05
Старые методы будут так же работать с 1-основными строками. Поэтому все старое будет работать по старому.

А новые как раз позволяют свести работу с любым типом строк к работе с 0-основным. И лучше свои новые работы переводить на использование этого хэлпера.
Александр
12.10.2012 в 16:39
загулушка?

Андрей, добавь на сайт чего-нибудь для удобного репорта опечаток.
Akella
15.10.2012 в 16:08
Александр, Вы говорите об опечатках, а сами пишите о каких-то "репортах"...
Андрей
20.07.2015 в 17:51
На сколько я сейчас вижу реализации хелперов в XE5 и XE8, все заглушки починили. 0-индексные строки нужны для перехода в мобильные операционки, в инете уже достаточно много описаний этих нововведений.
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно