RTTI контексти в Delphi 2010 как работи и как да ги използвате
Delphi 2010 включва подобрена поддръжка за RTTI, известна още като информация за типа на изпълнение или отражение. Много дизайнерски подходи преди това бяха налични само в управлявани езици като C# и Java, тъй като те изискват анотация на кода и интроспекция. Това вече е възможно и в света на Delphi.
Интересното в работата на RTTI е неговият подход към обединяването на обекти. Delphi е език без събиране на отпадъци, така че потребителите трябва да бъдат внимателни и да унищожават обекти, когато вече не са необходими, или като го правят изрично, или чрез създаване или използване на някакъв вид схема за собственост (на обекти), като тези, използвани от TComponent, където собственикът (Собственикът) поема отговорността за унищожаването на обектите.
Редът, в който се използва информацията за типа, не се вписва добре в схемата за собственост в стил TComponent. Обикновено, когато работите с RTTI, трябва да търсите обекти, които ви интересуват, да направите нещо с тях и да продължите да работите по-нататък. Това означава, че много обекти могат да бъдат дефинирани за валидиране, но реално не използвани. Управлението на жизнения цикъл на тези обекти трябва да е доста досадно, така че се приема различен подход: единен глобален RTTI пул от обекти. Докато поне един RTTI контекст е активен в програмата, обектният пул поддържа всички свои обекти актуални. Когато последният контекст излезе извън обхвата, обектите се освобождават.
Управлението на пула използва запис на Delphi за работа, който съдържа препратка към интерфейса. Когато даден контекст на RTTI се използва за първи път, той се поставя в тази препратка къминтерфейс. Той се поставя там само първия път, веднъж, защото записите на Delphi не поддържат конструктори по подразбиране, които също имат свои собствени проблеми. Например, как се справяте с изключенията в конструктора по подразбиране във всички точки, където могат да се появят? Създаване на масиви, локални променливи за нишки, глобални променливи, глобални променливи в модули, временни обекти в изрази и т.н. Това може да стане грозно, а в C++ понякога става.
Така че първото използване създава интерфейс, наречен пул токен. Той действа като манипулатор с преброени препратки, сочещ към глобалния обектен пул. Докато този интерфейс е актуален (съществува), глобалният набор от обекти ще остане актуален. Дори ако RTTI контекстът е копиран някъде, вградената логика за управление на интерфейса на Delphi, създадена на базата на принципите на COM, гарантира, че интерфейсът няма да бъде преждевременно изтрит, броят на препратките ще има правилната стойност. И когато контекстът на RTTI излезе извън обхвата, или като локална променлива във функция, която е приключила, или като поле в отдалечен обект, броят на препратките ще намалее. Когато броят на препратките достигне нула, пулът се изпразва.
Най-голямото предимство на този подход е, че използването на RTTI по същество трябва да бъде лесно и интуитивно. Необходимо е само да декларирате променлива от съответния тип в програмния код и да започнете да я използвате:
процедура Foo; вар. ctx: TRttiContext; t: TRttiType; започнете t := ctx.GetType(TypeInfo(Integer)); Writeln(t.Name); край;
Недостатъкът обаче е, че мързеливата инициализация може да причини грешка. Представете ситакъв сценарий:
1. Библиотека A декларира RTTI контекст A.C 2. Потребителски код B декларира RTTI контекст B.C 3. Код B изисква някои RTTI обекти O от B.C, за да ги предаде на библиотека A 4. B.C излиза извън обхвата 5. Библиотека A сега се опитва да работи с O, но открива за своя изненада, че обектите са били преждевременно изтрити, въпреки че A вече има RTTI контекст A.C
Проблемът е, че A никога не е използвал AC, така че маркерът на пула не е създаден. Когато B.C използва своя контекст, пулът възниква и към него са присвоени O обекти; но след като B.C излезе от обхвата, обектите бяха освободени.
Решението на този проблем е да се уведоми библиотека А, че използва дълготраен RTTI контекст и че очаква да взаимодейства с код на трета страна, който създава обекти от собствения си RTTI контекст и ги предава обратно, тя трябва да е сигурна, че е създаден маркер на пула за този дълготраен контекст. Лесен начин да направите това изглежда така:
тип TFooManager = клас FCtx: TRttiContext; // . конструктор Create; // . край;