The Road to Delphi

Delphi – Free Pascal – Oxygene

Exploring Delphi XE3 – Record Helpers for simple types – System.SysUtils.TStringHelper

19 Comments

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

Author: Rodrigo

Just another Delphi guy.

19 thoughts on “Exploring Delphi XE3 – Record Helpers for simple types – System.SysUtils.TStringHelper

  1. 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.

  2. 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. :)

    • 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.

  3. 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

  4. Why not just call that stuff a “type helper”? Seems to be the obvious choice for such a thing.

  5. I like to see new OO type code constructs available. OOP is object-oriented after all.

  6. Ist there also the possibility to define a helper for arrays?

    • Yes for array types, works just fine

      
      type
        TArrayInteger = array of Integer;
        TArrayHelper = record helper for TArrayInteger
        public
          function  Length : integer;
        end;
      
      
      function TArrayHelper.Length: integer;
      begin
       Exit(System.Length(Self));
      end;
      
  7. I wish they would finally implement interface helper and remove the “only one” restriction.

  8. 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!

  9. Pingback: Michael Swindell » Record Helpers for Simple Types: Best reason to upgrade to XE3?

  10. Pingback: Te Waka o Pascal · Adventures in Syntax: Something Old, Something New etc…

  11. Pingback: Novedades en la RTL: ¿qué cambia en XE3? | Delphi Básico

  12. Pingback: Mark Edington’s Delphi Blog : XE3 RTL Changes: A closer look at TStringHelper

  13. Pingback: Novedades en la RTL: ¿qué cambia en XE3?

  14. 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

  15. Pingback: Novedades en la RTL: ¿qué cambia en XE3? | Delphi Básico

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s