Sign in Join
Wiki copytemporary

Helper types: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Helper types

OldNewLine
1+Helper "types" allow you to extend the scope in which the compiler searches for symbols. This page describes helper types in Object Pascal. For the equivalent in Objective Pascal see [[FPC PasCocoa|here]].
2+
3+## General
4+
5+Class and Record helpers are available since the release of FPC 2.6, Type helpers since the release of FPC 3.0.
6+
7+## Declaration
8+
9+Helper types (which are **not** themselves concrete types of any kind despite appearing as such, and in fact amount to "syntactic sugar") allow you to extend a given class, interface, record or primitive type with additional functionality. So you can add
10+
11+- properties
12+- enumerators
13+- wrappers for existing functionality
14+- class vars and const
15+- local types
16+- constructors, class constructors, class destructors.
17+
18+You cannot add new fields.
19+
20+### Syntax
21+
22+Currently helper types can be used to extend classes (*class helper*), records (*record helper*) and (primitive) types (*type helper*) as those are supported by Delphi as well. Additionally, they can extend interfaces, (*type helper*) which is functionality unique to FPC.
23+
24+The general syntax for helper types is the following:
25+
26+```bnf
27+HelperName = class|record|type helper[(OptionalBaseHelper)] for TypeName
28+ [properties, procedures, functions, constructors, consts, vars]
29+end [hint modifiers];
30+```
31+
32+The sequence *class helper* declares a helper for a class, *record helper* declares a helper for a record while *type helper* declares a helper for a primitive type. The inheritance *(BaseHelper)* is optional.
33+
34+*HelperName*, *BaseHelper* and *TypeName* have to be valid Pascal identifiers. In case of a class helper *TypeName* must be a class and *BaseHelper* must be another class helper that extends either the same class as *TypeName* or a parent class of it. For record helpers *TypeName* must be a record and *BaseHelper* must be a record helper that extend the same record type. The declarations inside a helper are very similiar to the declaration of a normal class, but you must not use fields, destructors and class constructors/destructors.
35+
36+*Note*: In mode ObjFPC record helpers need the modeswitch *advancedrecords* to be declared; in mode Delphi no further modeswitch is necessary. For type helpers the modeswitch *typehelpers* is required.
37+
38+### Implementation
39+
40+The rules when implementing a helper are a bit differently then when implementing a normal class or record. First of the type of *Self* is always of the type of the extended type (e.g. *TObject* in case of a helper declared as *class helper for TObject*).
41+
42+If a method is not defined in the helper type itself it is
43+
44+1. first searched in all the ancestor helpers of the current helper
45+2. then in the extended type (helper for),
46+3. and then (for class helpers) in the ancestors of the extended class. Please note that if there is a helper for an ancestor it is searched right before the ancestor class itself is searched.
47+
48+#### Inherited with function name
49+
50+The **inherited FuncName;** skips the ancestors of the helper and starts searching in the extended type, then it proceeds normally, i.e. if the extended type has an ancestor the compiler searches in the helper for the ancestor of the extended type (if available), then in the ancestor of the extended type (if available), and so forth.
51+
52+#### Inherited without function name
53+
54+The **inherited;** without an explicit name can be a bit confusing:
55+
56+- FPC 3.3.1 and below searches first in the extended type then proceeds normally. That means it skips the helper and its ancestors. \$mode objfpc and delphi!
57+- Delphi even skips the extended type and searches in the helper for the ancestor of the extended type (if available), then in the ancestor of the extended type (if available). That means it skips the helper, its ancestors and the extended type.
58+
59+### Restrictions
60+
61+A helper type may not
62+
63+- contain (class) destructors (except in trunk FPC 3.3.1)
64+- contain class constructors (except in trunk FPC 3.3.1)
65+- contain fields
66+- contain abstract methods
67+- "override" virtual methods of the extended class (they can be hidden by the helper though)
68+
69+Methods of the extended type can be overloaded (thus they are not hidden by the helper) by using the *overload* keyword.
70+
71+Example:
72+
73+``` pascal
74+{$mode objfpc}
75+{$MODESWITCH ADVANCEDRECORDS}
76+
77+TObjectHelper = class helper for TObject
78+ function ToString(const aFormat: String): String; overload;
79+end;
80+
81+function TObjectHelper.ToString(const aFormat: String): String;
82+begin
83+ Result := Format(aFormat, [ToString]);
84+end;
85+
86+var
87+ o: TObject;
88+begin
89+ Writeln(o.ToString('The object''s name is %s'));
90+end.
91+```
92+
93+### Inheritance
94+
95+So-called "inheritance" for helper types fulfills two purposes:
96+
97+- use the methods of multiple helpers at once
98+- (class helpers only) bring methods that were added to a parent class to the topmost visibility (because equally named methods of helpers are hidden by subclasses)
99+
100+#### Class Helper
101+
102+A class helper can inherit from another class helper if it extends a subclass of the class or the same class which is extend by the parent class helper.
103+
104+Example:
105+
106+``` pascal
107+TObjectHelper = class helper for TObject
108+ procedure SomeMethod;
109+end;
110+
111+TFoo = class(TObject)
112+end;
113+
114+TFooHelper = class helper(TObjectHelper) for TFoo
115+end;
116+```
117+
118+#### Record Helper
119+
120+A record helper can inherit from another record helper if it extends the same record which is extend by the parent record helper. **Important**: Inheritance for record helpers is not enabled in mode Delphi, because there this is not allowed.
121+
122+Example:
123+
124+``` pascal
125+TTest = record
126+end;
127+
128+TTestHelper = record helper for TTest
129+ procedure SomeMethod;
130+end;
131+
132+TTestHelperSub = record helper(TTestHelper) for TTest
133+end;
134+```
135+
136+#### Type Helper
137+
138+A type helper can inherit from another type helper if it extends the same type which is extended by the parent type helper.
139+
140+``` pascal
141+{$modeswitch TypeHelpers}
142+
143+TTypeHelper = type helper for Integer
144+ procedure SetZero;
145+end;
146+
147+TTypeHelperSub = type helper(TTypeHelper) for integer
148+end;
149+
150+implementation
151+
152+procedure TTypeHelper.SetZero;
153+begin
154+ self:= 0;
155+end;
156+```
157+
158+Since the introduction, type helpers can for example be found in section \[//www.freepascal.org/docs-html/rtl/sysutils/typehelpers.html type helpers\] of unit sysutils.
159+
160+### Example
161+
162+A simple example for extending existing functionality is the following: The LCL's *TCheckListBox*, which is a list with checkboxes, does not provide a property to get the count of all checked items. Using a class helper this can be added rather easily.
163+
164+``` pascal
165+type
166+ TCheckListBoxHelper = class helper for TCheckListBox
167+ private
168+ function GetCheckedCount: Integer;
169+ public
170+ property CheckedCount: Integer read GetCheckedCount;
171+ end;
172+
173+function TCheckListBoxHelper.GetCheckedCount: Integer;
174+var
175+ i: Integer;
176+begin
177+ Result := 0;
178+ for i := 0 to Items.Count - 1 do
179+ if Checked[i] then
180+ Inc(Result);
181+end;
182+
183+// somewhere else (TCheckListBoxHelper needs to be in scope)
184+if MyCheckList.CheckedCount > 0 then ...
185+```
186+
187+Please note that it's not trivial to implement a cached variation, as helper types can't introduce fields.
188+
189+Here's another example. This converts array of inter to string with commas, and vice versa.
190+
191+``` pascal
192+type
193+ TIntArray = array of integer;
194+
195+ { HIntArray }
196+
197+ HIntArray = type helper for TIntArray
198+ private
199+ function getCommaText: string;
200+ procedure setCommaText (AValue: string);
201+ public
202+ property CommaText: string read getCommaText write setCommaText;
203+ end;
204+
205+implementation
206+
207+{ HIntArray }
208+
209+function HIntArray.getCommaText: string;
210+var
211+ ti: integer;
212+begin
213+ if Length(Self) >= 1 then Result:= IntToStr(Self[0]) else Exit('');
214+
215+ for ti := 1 to High(Self) do Result += Format(',%d', [Self[ti]]);
216+end;
217+
218+procedure HIntArray.setCommaText(AValue: string);
219+var
220+ arr: TStringArray;
221+ i: integer;
222+begin
223+ Self:= [];
224+ arr:= AValue.Split([',']);
225+ for i:= Low(arr) to High(arr) do
226+ Concat(Self, [StrToIntDef(arr[i], -1)]);
227+end;
228+
229+//----------------------------------------------------
230+
231+procedure TForm1.Button4Click(Sender: TObject);
232+var
233+ arr: TIntArray = (1,2,3);
234+begin
235+ showmessage(arr.CommaText); // this will display "1,2,3"
236+end;
237+```
238+
239+## Usage
240+
241+### Scope
242+
243+The methods declared inside the helper are always available once it is in scope and can be called as if they'd belong to the extended type.
244+
245+Example:
246+
247+``` pascal
248+type
249+ TObjectHelper = class helper for TObject
250+ function TheAnswer: Integer;
251+ end;
252+
253+function TObjectHelper.TheAnswer: Integer;
254+begin
255+ Result := 42;
256+end;
257+
258+begin
259+ o := TObject.Create;
260+ o.TheAnswer;
261+end.
262+```
263+
264+By default, only one helper type can be active for a type at the same time. This helper is always the last one that is in scope (which follows the usual rules of Pascal).
265+
266+Example:
267+
268+``` pascal
269+type
270+ TTestRecord = record
271+ end;
272+ TTestClass = class
273+ end;
274+
275+ TTestRecordHelper = record helper for TTestRecord
276+ procedure Test;
277+ end;
278+
279+ TTestClassHelper1 = class helper for TTestClass
280+ procedure Test1;
281+ end;
282+
283+ TTestClassHelper2 = class helper for TTestClass
284+ procedure Test2;
285+ end;
286+
287+var
288+ tr: TTestRecord;
289+ tc: TTestClass;
290+begin
291+ tr.Test; // this compiles
292+ tc.Test1; // this won't compile
293+ tc.Test2; // this compiles
294+end.
295+```
296+
297+### Method hiding
298+
299+The methods of helper types hide the methods declared in the extended type as long as they are not declared with "overload".
300+
301+If a class helper is in scope for a parent class then only the methods of the parent class are hidden while the methods of the subclass will stay visible.
302+
303+Example:
304+
305+``` pascal
306+type
307+ TTest = class
308+ procedure Test;
309+ end;
310+ TTestSub = class(TTest)
311+ procedure Test;
312+ end;
313+
314+ TTestHelper = class helper for TTest
315+ procedure Test;
316+ end;
317+
318+var
319+ t: TTest;
320+ ts: TTestSub;
321+begin
322+ t.Test; // this calls "Test" of the helper
323+ ts.Test; // this calls "Test" of TTestSub
324+end.
325+```
326+
327+Here is another example to explain the search order:
328+
329+``` pascal
330+type
331+ TAnimal = class ... end;
332+ TBird = class ... end;
333+ TAnimalHelper = class helper for TAnimal ... end;
334+ TBirdHelper = class helper for TBird ... end;
335+ TEagleHelper = class helper(TBirdHelper) for TBird ...end;
336+var b: TBird;
337+begin
338+ b.Name;
339+end;
340+```
341+
342+The compiler searches *Name* in this order: TEagleHelper, TBirdHelper, TBird, TAnimalHelper, TAnimal.
343+
344+### Restrictions
345+
346+You can not reference helper types anywhere in the code except for the following cases:
347+
348+- inheriting from one
349+- using one in (Bit)SizeOf
350+- using one in TypeInfo
351+
352+## Differences
353+
354+### Differences between mode Delphi and ObjFPC
355+
356+In mode Delphi you can use *virtual*, *dynamic*, *override* and *message* identifiers. As the concepts behind those identifiers (virtual methods, message dispatching) isn't applicable for helpers, those keywords are ignored in mode Delphi and not allowed in mode ObjFPC.
357+
358+In mode Delphi you can't inherit a record helper from another one and you can not use the "inherited" keyword (not even to call the method of the extended record).
359+
360+### Differences between Delphi and FPC
361+
362+While the helper feature was implemented as Delphi compatible as possible there are some differences between the two:\
363+
364+#### Root type
365+
366+In Delphi a helper is basically a class which inherits from a class *TClassHelperBase* (both class and record helpers) which in turn inherits from *TInterfacedObject*. In FPC helpers are a type for themselves and don't have a basetype.\
367+As this difference is only visible in the RTTI (which is seldom used for helpers) this difference was considered negligible.
368+
369+#### inherited
370+
371+See [[Helper types|Inherited]]
372+
373+#### RTTI
374+
375+Because helpers are their own type in FPC they also have a custom type kind (*tkHelper*) and their own fields in TTypeData:
376+
377+- HelperParent: a PTypeInfo field that points to the type info of the parent helper (can be **nil**)
378+- ExtendedInfo: a PTypeInfo field that points to the type info of the extended type
379+- HelperProps: contains the count of the (published) properties the helper contains
380+- HelperUnit: contains the name of the unit the helper is defined in\
381+
382+The usual RTTI methods can be used to query for properties of the helper.
383+
384+*Note*: *ExtendedInfo* and *HelperUnit* don't have a Delphi equivalent.
385+
386+Example:
387+
388+``` pascal
389+{$mode objfpc}
390+uses typinfo;
391+type
392+ TObjectHelper = class helper for TObject
393+ end;
394+
395+var
396+ ti: PTypeInfo;
397+ td: PTypeData;
398+begin
399+ ti := TypeInfo(TObjectHelper);
400+ td := GetTypeData(ti);
401+{$ifdef fpc}
402+// you must use this in FPC
403+if ti^.Kind = tkHelper then begin
404+ if Assigned(td^.HelperParent) then
405+ Writeln(td^.HelperParent^.Name)
406+ else
407+ Writeln('no helper parent');
408+ Writeln(td^.ExtendedInfo^.Name);
409+ Writeln(td^.HelperProps);
410+ Writeln(td^.HelperUnit);
411+end;
412+{$else}
413+// you must use this in Delphi
414+if ti^.Kind = tkHelper then begin
415+ if td^.ParentInfo <> TypeInfo(TClassHelperBase) then
416+ Writeln(td^.HelperParent^.Name)
417+ else
418+ Writeln('no helper parent');
419+ Writeln(td^.PropCount);
420+end;
421+{$endif}
422+end.
423+```
424+
425+## Code examples
426+
427+The following table lists some examples for class helpers found on the web and whether they work with the current implementation.
428+
429+| URL | State |
430+|----|----|
431+| [Class helper to add for ... in support for TComponent.Components / ComponentCount](http://wiert.wordpress.com/2009/05/07/delphi-class-helper-to-add-for-%E2%80%A6-in-support-for-tcomponentcomponentscomponentcount/) | ok |
432+| [Class helper for Delphi's TStrings: Implemented Add(Variant)](http://delphi.about.com/od/delphitips2008/qt/tstrings-helper.htm) | ok |
433+
434+## Multi-scope helpers
435+
436+In trunk FPC, it is possible to allow all helpers of a type to be in scope at the same time instead of only the last one found, which can be enabled with {\$modeswitch multiscopehelpers}
437+
438+### Order/Precedence
439+
440+With multihelpers disabled (the default) the compiler searches in the last matching helper and its ancestors and then in the extended type.
441+
442+With multihelpers enabled the compiler searches in the last matching helper and its ancestors, then in the earlier matching helper(s) and its ancestors and then in the extended type.
443+
444+``` pascal
445+{$modeswitch multihelpers}
446+type
447+ TAnimal = class ... end;
448+ TBird = class ... end;
449+ TAnimalHelper = class helper for TAnimal ... end;
450+ TBirdHelper = class helper for TBird ... end;
451+ TExtBirdHelper = class helper for TBird ... end;
452+ TEagleHelper = class helper(TBirdHelper) for TBird ...end;
453+var b: TBird;
454+begin
455+ b.Name;
456+end;
457+```
458+
459+The compiler searches *Name* in this order: TEagleHelper, TBirdHelper, TExtBirdHelper, TBird, TAnimalHelper, TAnimal.
460+
461+### Inherited
462+
463+**inherited** keyword works the same with multihelpers.
464+
465+---
466+*From the Free Pascal wiki, page "Helper types", as it stood on 2023-11-25 after a change by Kjteng, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
467+Category: Data types

Back to history