Delphi X3 introduces a very cool language extension, which is Record Helpers for simple types. So you can add a set of fields, properties or methods to strings, integers, Double, TDateTime and so on.
In this post I will show you the TStringHelper record helper for the string type defined in the System.SysUtils unit. Take a look to the definition of this record helper.
TStringHelper = record helper for string private function GetChars(Index: Integer): Char; inline; function GetLength: Integer; inline; class function CharInArray(const C: Char; const InArray: array of Char): Boolean; static; function IndexOfAny(const Values: array of string; var Index: Integer): Integer; overload; public const Empty = ''; // Methods class function Create(C: Char; Count: Integer): string; overload; inline; static; class function Create(const Value: array of Char; StartIndex: Integer; Length: Integer): string; overload; static; class function Create(const Value: array of Char): string; overload; static; class function Compare(const StrA: string; const StrB: string): Integer; overload; static; class function Compare(const StrA: string; const StrB: string; IgnoreCase: Boolean): Integer; overload; static; class function Compare(const StrA: string; IndexA: Integer; const StrB: string; IndexB: Integer; Length: Integer): Integer; overload; static; class function Compare(const StrA: string; IndexA: Integer; const StrB: string; IndexB: Integer; Length: Integer; IgnoreCase: Boolean): Integer; overload; static; class function CompareOrdinal(const strA: string; const strB: string): Integer; overload; static; class function CompareOrdinal(const strA: string; indexA: Integer; const strB: string; indexB: Integer; length: Integer): Integer; overload; static; function CompareTo(const strB: string): Integer; function Contains(const Value: string): Boolean; class function Copy(const Str: string): string; inline; static; procedure CopyTo(SourceIndex: Integer; var destination: array of Char; DestinationIndex: Integer; Count: Integer); class function EndsText(const ASubText, AText: string): Boolean; static; function EndsWith(const Value: string): Boolean; overload; function EndsWith(const Value: string; IgnoreCase: Boolean): Boolean; overload; function Equals(const Value: string): Boolean; overload; class function Equals(const a: string; const b: string): Boolean; overload; static; class function Format(const Format: string; const args: array of const): string; overload; static; function GetHashCode: Integer; function IndexOf(value: Char): Integer; overload; inline; function IndexOf(const Value: string): Integer; overload; inline; function IndexOf(Value: Char; StartIndex: Integer): Integer; overload; function IndexOf(const Value: string; StartIndex: Integer): Integer; overload; function IndexOf(Value: Char; StartIndex: Integer; Count: Integer): Integer; overload; function IndexOf(const Value: string; StartIndex: Integer; Count: Integer): Integer; overload; function IndexOfAny(const AnyOf: array of Char): Integer; overload; function IndexOfAny(const AnyOf: array of Char; StartIndex: Integer): Integer; overload; function IndexOfAny(const AnyOf: array of Char; StartIndex: Integer; Count: Integer): Integer; overload; function Insert(StartIndex: Integer; const Value: string): string; function IsDelimiter(const Delimiters: string; Index: Integer): Boolean; function IsEmpty: Boolean; class function IsNullOrEmpty(const Value: string): Boolean; static; class function IsNullOrWhiteSpace(const Value: string): Boolean; static; class function Join(const Separator: string; const values: array of const): string; overload; static; class function Join(const Separator: string; const Values: array of string): string; overload; static; class function Join(const Separator: string; const Values: IEnumerable): string; overload; static; class function Join(const Separator: string; const value: array of string; StartIndex: Integer; Count: Integer): string; overload; static; function LastDelimiter(const Delims: string): Integer; function LastIndexOf(Value: Char): Integer; overload; function LastIndexOf(const Value: string): Integer; overload; function LastIndexOf(Value: Char; StartIndex: Integer): Integer; overload; function LastIndexOf(const Value: string; StartIndex: Integer): Integer; overload; function LastIndexOf(Value: Char; StartIndex: Integer; Count: Integer): Integer; overload; function LastIndexOf(const Value: string; StartIndex: Integer; Count: Integer): Integer; overload; function LastIndexOfAny(const AnyOf: array of Char): Integer; overload; function LastIndexOfAny(const AnyOf: array of Char; StartIndex: Integer): Integer; overload; function LastIndexOfAny(const AnyOf: array of Char; StartIndex: Integer; Count: Integer): Integer; overload; function PadLeft(TotalWidth: Integer): string; overload; inline; function PadLeft(TotalWidth: Integer; PaddingChar: Char): string; overload; inline; function PadRight(TotalWidth: Integer): string; overload; inline; function PadRight(TotalWidth: Integer; PaddingChar: Char): string; overload; inline; function Remove(StartIndex: Integer): string; overload; inline; function Remove(StartIndex: Integer; Count: Integer): string; overload; inline; function Replace(OldChar: Char; NewChar: Char): string; overload; function Replace(OldChar: Char; NewChar: Char; ReplaceFlags: TReplaceFlags): string; overload; function Replace(const OldValue: string; const NewValue: string): string; overload; function Replace(const OldValue: string; const NewValue: string; ReplaceFlags: TReplaceFlags): string; overload; function Split(const Separator: array of Char): TArray; overload; function Split(const Separator: array of Char; Count: Integer): TArray; overload; function Split(const Separator: array of Char; Options: TStringSplitOptions): TArray; overload; function Split(const Separator: array of string; Options: TStringSplitOptions): TArray; overload; function Split(const Separator: array of Char; Count: Integer; Options: TStringSplitOptions): TArray; overload; function Split(const Separator: array of string; Count: Integer; Options: TStringSplitOptions): TArray; overload; function StartsWith(const Value: string): Boolean; overload; function StartsWith(const Value: string; IgnoreCase: Boolean): Boolean; overload; function Substring(StartIndex: Integer): string; overload; function Substring(StartIndex: Integer; Length: Integer): string; overload; function ToCharArray: TArray; overload; function ToCharArray(StartIndex: Integer; Length: Integer): TArray; overload; function ToLower: string; function ToLowerInvariant: string; function ToUpper: string; function ToUpperInvariant: string; function Trim: string; overload; function Trim(const TrimChars: array of Char): string; overload; function TrimEnd(const TrimChars: array of Char): string; function TrimStart(const TrimChars: array of Char): string; property Chars[Index: Integer]: Char read GetChars; property Length: Integer read GetLength; end;
Note : The same rules for the class and record helpers applies, so you can define multiple helpers with a single type. However, only zero or one helper applies in any specific location in source code and only the record helper defined in the nearest scope will apply. The record helper scope is determined in the normal Delphi fashion (for example, right to left in the unit’s uses clause).
As you can see most of the strings related methods now are part of the string type, so now you can write code like this.
{$APPTYPE CONSOLE} uses System.SysUtils; var s,s1 :string; begin try // Length property s:='Hello Delphi XE3'; Writeln(Format('the string Length is %d',[s.Length])); Writeln('The length of this literal string is '.Length); //function Contains if s.Contains('Delphi') then Writeln(Format('the string "%s" contains the string "%s"',[s,'Delphi'])); //function EndsWith if s.EndsWith('XE3') then Writeln(Format('the string "%s" ends with the string "%s"',[s,'XE3'])); //function ToLower Writeln(Format('using ToLower %s',[s.ToLower])); //function ToUpper Writeln(Format('using ToUpper %s',[s.ToUpper])); //function IndexOf Writeln(Format('The index of H is %d',[s.IndexOf('H')])); //the value is based in a zero index //function LastDelimiter Writeln(Format('The first occurence of any of these chars "abcdef" is %d',[s.IndexOfAny(['a','b','c','d','e','f'])])); //the value is based in a zero index //function LastDelimiter Writeln(Format('The last occurence of any of these chars "abcdef" is %d',[s.LastDelimiter('abcdef')])); //the value is based in a zero index //function Remove Writeln(Format('The string with only the first 5 chars is %s',[s.Remove(5)])); //the value is based in a zero index //function Replace Writeln(Format('Replacing the white spaces for "-", the string becomes %s',[s.Replace(' ','-')])); //the value is based in a zero index //function split Writeln; Writeln('Testing the split function'); for s1 in s.Split([' ']) do Writeln(s1); Writeln; //function Substring Writeln(Format('The sub string starting in the index 6 is "%s"',[s.Substring(6)])); //the value is based in a zero index except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; readln; end.
Note : All the methods and properties of the System.SysUtils.TStringHelper are zero based index, so be careful when you uses functions like CopyTo, IndexOf, IndexOfAny, Insert, Join, LastDelimiter, LastIndexOf, LastIndexOfAny, Remove, Substring, ToCharArray and the Chars property.
Finally exist a few set of record helpers included in the RTL code which you can use
- System.Classes – TUInt32Helper = record helper for UInt32
- System.SyncObjs – TCriticalSectionHelper = record helper for TRTLCriticalSection
- System.SyncObjs – TConditionVariableHelper = record helper for TRTLConditionVariable
- System.Mac.CFUtils – CFGregorianDateHelper = record helper for CFGregorianDate
- System.SysUtils – TGuidHelper = record helper for TGUID
- System – TSingleHelper = record helper for Single
- System – TDoubleHelper = record helper for Double
- System – TExtendedHelper = record helper for Extended
- Winapi.D2D1 – D2DMatrix3x2FHelper = record helper for TD2DMatrix3X2F
- Vcl.Themes – TElementMarginsHelper = record helper for TElementMargins
September 5, 2012 at 7:30 pm
I like this new functionality a lot but now that Embarcadero are providing general purpose record helpers it’s important they remove that single helper per record/class restriction.
September 5, 2012 at 8:18 pm
Good luck with that.
Helpers were created for a very specific and limited purpose but have been co-opted and coerced them into a more general purpose use for which they were not only not intended but were clearly and specifically documented as not being intended for.
I don’t know of course, but I doubt that the internals of the implementation will easily allow for the extension of the specific into the general. Presumably the “restriction” arising from the implementation targeting the specific use case for which they were designed made that implementation itself easier – you wouldn’t deliberately do more work in order to achieve less. At least, not if you were sensible.
But given that everyone was warned not to use helpers as a general purpose solution then no-one really has anyone to blame but themselves if they run into problems with aspects of their implementation arising from their original design intent.
Besides which, “record helpers” for non-record types ? Has a nasty code smell to me. :)
September 5, 2012 at 10:03 pm
Jolyon I’m very aware of your position against the class and records helpers and I respect your points of view, but let me tell you which on my personal experience never I got any problem using the class helpers In fact this language feature has allowed me to solve many problems, that without the existence of these helpers would be very difficult to solve. Finally I think which the inclusion of record helpers for simple types add a lot advantages to write more cleaner code.
September 5, 2012 at 8:45 pm
Jolyon; It’s a code smell this year, and it’s consistent OOP next year.
Python, C# and a lot of other languages support “Literal”.Length() and other similar syntactic sugar. I think it’s wonderful.
W
September 5, 2012 at 10:58 pm
To me the smell is more in the declaration syntax (“record helper” for non-records), and the one helper limitation (which those other languages, or other Pascal favors for that matter) don’t have.
The “.” chaining form is more readable in many cases.
September 6, 2012 at 10:40 am
Well, String, Double, Boolean,… are record types, aren’t they? :-)
September 6, 2012 at 2:40 am
Why not just call that stuff a “type helper”? Seems to be the obvious choice for such a thing.
September 6, 2012 at 11:33 am
I like to see new OO type code constructs available. OOP is object-oriented after all.
September 7, 2012 at 9:10 am
Ist there also the possibility to define a helper for arrays?
September 7, 2012 at 10:17 am
Yes for array types, works just fine
September 7, 2012 at 9:46 am
I wish they would finally implement interface helper and remove the “only one” restriction.
September 8, 2012 at 3:08 am
We all know that the current Delphi compiler is now legacy. The next generation compiler is under development. We will get our first sight of it this year.
My expectation is that next gen compiler won’t have helpers. Instead it will have first class extension methods a la .net.
Next gen will have a rooted type system and the methods currently in TStringHelper will be introduced in the declaration of string.
The record helpers in XE3 are primarily intended to help migrate code from legacy compiler to next gen.
Well, that’s all pure speculation and guesswork. So, I’ll probably be completely wrong!
Pingback: Michael Swindell » Record Helpers for Simple Types: Best reason to upgrade to XE3?
Pingback: Te Waka o Pascal · Adventures in Syntax: Something Old, Something New etc…
Pingback: Novedades en la RTL: ¿qué cambia en XE3? | Delphi Básico
Pingback: Mark Edington’s Delphi Blog : XE3 RTL Changes: A closer look at TStringHelper
Pingback: Novedades en la RTL: ¿qué cambia en XE3?
Pingback: Delphi Mobile (NEXTGEN) compiler: the risk of a changed TSymbolName; unsupported data types means unsupported RTTI as well « The Wiert Corner – irregular stream of stuff
Pingback: Novedades en la RTL: ¿qué cambia en XE3? | Delphi Básico