Конечно этот пост не вызовет особого интереса у «акул шоубиза 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»»
Полезная штучка. При сохранении в файл, например, сохранение перечисляемого типа строкой гораздо удобнее нежели сохранение его индекса, т.к. при изменении порядка при объявлении перечисляемого типа изменятся и индексы (если они не были присвоены при объявлении), что повлечет неверную интерпретацию типа при считывании.
спасибо большое!