уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Конечно этот пост не вызовет особого интереса у «акул шоубиза Delphi-кодинга», но блог ведь читают и те кто только пробуют разобраться в Delphi и создать что-то свое. Поэтому, думаю, что для таких людей пост «Применение перечисляемых типов Delphi» поможет сделать их приложения более…профессиональными что-ли :).
Не так давно, вдохновленный переводами Александра Божко статей о новом RTTI Delphi 2010 я решил засунуть свою лень поглубже..в портмоне и начать наконец-то разбираться с тем, что такое RTTI, как использовать и т.д. Вообще тема RTTI достаточно обширна и интересна, но сегодня речь пойдет о применении возможностей RunTime Type Information при работе с перечисляемым типом данных. А чтобы не останавливаться на голой теории, рассмотрим применим наши новые знания на практическом примере.

Итак, что такое RTTI.
Runtime Type Information или сокрашенно RTTI (информация о типах времени исполнения) —это данные, генерируемые компилятором Delphi об объектах программы. RTTI представляет собой возможность языка, обеспечивающее приложение информацией об объектах (имя, размер экземпляра, указатели на класс-предок, имя класса и т. д.) и о простых типах во время работы программы. Delphi, например, использует RTTI для доступа к значениям свойств компонент, сохраняемых и считываемых из dfm-файлов и отображения их в Object Inspector.
Основополагающие определения типов, основные функции и процедуры для работы с runtime информацией находятся в модуле TypInfo. Этот модуль содержит две фундаментальные структуры для работы с RTTI — TTypeInfo и TTypeData (типы указателей на них — PTypeInfo и PTypeData соответственно). Попробуем использовать их на конкретном примере.
Допустим, нам необходимо сформировать строку-запрос к тому же Google, чтобы получить доступ к API. Что мы можем почерпнуть из документации по ClientLogin?
1. Это то, что такие параметры как AccountType и Service могут принимать вполне конкретные значения, которые можно представить в виде двух перечисляемых типов данных:

type
  TAccountType = (GOOGLE, HOSTED, HOSTED_OR_GOOGLE);
 
type
  TServices = (xapi,analytics,apps,gbase,jotspot,blogger,print,cl,codesearch,
  cp,writely,finance,mail,health,local,lh2,annotateweb,wise,sitemaps,
  youtube);

2. Ответ сервера также может содержать ограниченное количество значений (кодов) ошибок и 1 значение («Ok») при успешной аутентификации. Следовательно получим ещё один тип данных:

type
  TErrorCodes = (Ok,BadAuthentication,NotVerified,TermsNotAgreed,
               CaptchaRequired,Unknown,AccountDeleted,AccountDisabled,
                ServiceDisabled,ServiceUnavailable);

Теперь, когда все перечисляемые типы данных определены, возникает простой вопрос «Что дальше?». Как бы мы поступили, например, при формировании строки запроса, если б абсолютно ничего не знали про RTTI и модуль TypInfo в частности?
Как минимум, мы бы организовали, что-то наподобие этого:

var Param:string;
     AccType: TAccountType;
begin
   case AccType of
      GOOGLE: ParamStr:='что-то там&AccauntType=GOOGLE'; 
      HOSTED: ParamStr:='что-то там&AccauntType=HOSTED';
      HOSTED_OR_GOOGLE: ParamStr:='что-то там&AccauntType=HOSTED_OR_GOOGLE';
   end;
end;

Примерно то же самое и в отношении TServices. Будет ли этот код работать? Конечно будет…куда он нафиг денется. Но ведь можно сделать и по другому.
В модуле TypInfo определен следующий метод:

function GetEnumName(TypeInfo: PTypeInfo; Value: Integer): string;

Позволяет получить значение перечисляемого типа в виде простой строки. Первый параметр — указатель на запись TTypeInfo, второй — порядковый номер значения в перечисляемом типе данных.
То есть выполнив следующий код:

ShowMessage(GetEnumName(TypeInfo(TAccountType),1));

Мы получим сообщение, содержащее строку «HOSTED». Теперь применив этот метод, мы можем сократить код представленный выше всего до одной строки:

var Param:string;
     AccType: TAccountType;
begin
  ParamStr:='что-то там&AccountType='+GetEnumName(TypeInfo(TAccountType),ord(AccType));   
end;

Или, если Вы используете какой-то шаблон для формирования строки, то так:

var Param:string;
     AccType: TAccountType;
begin
  ParamStr:=Format(Shablon,[GetEnumName(TypeInfo(TAccountType),ord(AccType)]);   
end;

Теперь, допустим, что мы сформировали строку запроса, отправили запрос на сервер и получили обратно ответ, содержащий «Ок» или строковый код ошибки. И нам необходимо некой переменной типа TErrorCodes присвоить значение в соответствии с ответом. Опять же, как бы мы поступили без знаний о TypInfo, получив, например код ошибки «NotVerified»? Кто-то начал бы расписывать кучу if..then..else, или использовать метод AnsiIndexStr из модуля StrUtils, чтобы потом сопоставить полученное значение со значением порядкового типа примерно так:

var ErrCode: TErrorCodes;
begin
   ErrCode:=TErrorCodes(AnsiIndesStr(MassivStrok,"NotVerified"));
end;

Опять же работать всё будет, но зачем нам таскать за собой лишний модуль StrUtils в uses, когда у нас уже есть TypInfo? Воспользуемся ещё одним методом для работы с перечисляемыми типами:

function GetEnumValue(TypeInfo: PTypeInfo; const Name: string): Integer;

Метод возвращает порядковый номер значения перечисляемого типа. На вход подается все тот же указатель на TTypeInfo и значение порядкового типа в виде строки. Как раз то, что нам и надо. В результате применения GetEnumValue приведенный выше фрагмент кода можно представить так:

  ErrCode:=TErrorCodes(GetEnumValue(TypeInfo(TErrorCodes),ErrorText));

Где ErrorText — это строка с кодом ошибки, которую возвращает нам сервер.
Не знаю на сколько возрастет или наоборот — упадет скорость работы программы при использовании TypInfo — не проверял, но работать с RTTI мне показалось намного удобнее, чем каждый раз делать «финт ушами» определяя индексы в строковых массивах, перебирая значения и т.д. Думаю теперь ещё более плотно займусь изучением новых типов Delphi 2010 — TValue b пр.

08.05.02
UPDATE: небольшое дополнение — функции GetEnumValue и GetEnumName не генерируют исключения в случаях, если вы зададите вторым параметром неверную строку или не существующий индекс элемента. В первом случае GetEnumValue просто вернет вам значение 255, а GetEnumName непонятную строку.

18.08.2017 

UPDATE: Если элементы перечисляемого типа начинаются не с 0, как обычно, а, например, с 3, то получаем при компиляции ошибку «ERROR: «E2134: Type ‘TMyEnum’ has no typeinfo»»

5 1 голос
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
2 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
r3code
19/05/2010 08:51

Полезная штучка. При сохранении в файл, например, сохранение перечисляемого типа строкой гораздо удобнее нежели сохранение его индекса, т.к. при изменении порядка при объявлении перечисляемого типа изменятся и индексы (если они не были присвоены при объявлении), что повлечет неверную интерпретацию типа при считывании.

Валерон
Валерон
24/03/2016 14:05

спасибо большое!