Delphi の型をレコードヘルパで拡張する

# はじめに
Delphi には 2006 から[クラスヘルパとレコードヘルパ](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AF%E3%83%A9%E3%82%B9_%E3%83%98%E3%83%AB%E3%83%91%E3%81%A8%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89_%E3%83%98%E3%83%AB%E3%83%91%EF%BC%88Delphi%EF%BC%89)があり、既存のクラスやレコード (構造体) を継承する事なく拡張する機能があります。あ、レコード (構造体) はそもそも継承できないんですけどね。

そして Delphi XE4 において普通の型ですらレコードヘルパで拡張できるようになりました。10.2 Tokyo 時点では以下のような組み込み型ヘルパがあります。

| レコードヘルパ|説明|
|:-----------------|:------------------|
|[TBooleanHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TBooleanHelper)|TBooleanHelper は、Boolean 型の機能を提供するレコード ヘルパです。|
|[TByteBoolHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TByteBoolHelper)|TByteBoolHelper は、ByteBool 型の機能を提供するレコード ヘルパです。|
|[TByteHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TByteHelper)|TByteHelper は、Byte 型の機能を提供するレコード ヘルパです。|
|[TCardinalHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TCardinalHelper)|TCardinalHelper は、Cardinal 型の機能を提供するレコード ヘルパです。|
|[TDoubleHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TDoubleHelper)|単精度浮動小数点値の低レベル操作の実行をサポートします。|
|[TExtendedHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TExtendedHelper)|拡張精度（Extended 型）浮動小数点値に対する低レベル演算を実行できるようにします。|
|[TGuidHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TGuidHelper)|TGUID のヘルパ型です。|
|[TInt64Helper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TInt64Helper)|TInt64Helper は、Int64 型の機能を提供するレコード ヘルパです。|
|[TIntegerHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TIntegerHelper)|TNativeUIntHelper は、NativeUInt 型の機能を提供するレコード ヘルパです。|
|[TLongBoolHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TLongBoolHelper)|TLongBoolHelper は、LongBool 型の機能を提供するレコード ヘルパです。|
|[TNativeIntHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TNativeIntHelper)|TNativeIntHelper は、NativeInt 型の機能を提供するレコード ヘルパです。|
|[TNativeUIntHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TNativeUIntHelper)|TNativeUIntHelper は、NativeUInt 型の機能を提供するレコード ヘルパです。|
|[TShortIntHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TShortIntHelper)|TShortIntHelper は、ShortInt 型の機能を提供するレコード ヘルパです。|
|[TSmallIntHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TSmallIntHelper)|TSmallIntHelper は、SmallInt 型の機能を提供するレコード ヘルパです。|
|[TStringHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TStringHelper)|インデックスが 1 から始まる文字列もインデックスが 0 から始まる文字列も含むすべての文字列扱うための関数やプロパティを提供するレコード ヘルパです。|
|[TUInt64Helper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TUInt64Helper)|TUInt64Helper は、UInt64 型の機能を提供するレコード ヘルパです。|
|[TWordBoolHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TWordBoolHelper)|TWordBoolHelper は、WordBool 型の機能を提供するレコード ヘルパです。|
|[TWordHelper](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.TWordHelper)|TWordHelper は、Word 型の機能を提供するレコード ヘルパです。|

例えば以前は [IntToStr()](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.IntToStr) を使って数値->文字列変換しなくてはなりませんでした。

```pascal:
var
  i: Integer;
  s: string;
begin
  i := 100;
  s := IntToStr(i);
end;
```

現在では以下のように書けます。

```pascal:
var
  i: Integer;
  s: string;
begin
  i := 100;
  s := i.ToString;
end;
```
コード補完が効くので便利ですね。

# レコードヘルパを自作する

例えば [TDateTime 型](http://docwiki.embarcadero.com/Libraries/ja/System.TDateTime)の実体は Double 型です。T が頭にありますがレコード (構造体) ではないため、Delphi XE4 より前ではヘルパが作れませんでしたが、今なら？

```pascal:uDateTime.pas
unit uDateTime;

interface

uses
  System.SysUtils, System.DateUtils;

type
  TDayOfWeek = (Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday);

  TDayOfWeekHelper = record helper for TDayOfWeek
    function ToInteger: Integer;
  end;

  TDateTimeHelper = record helper for TDateTime
    function AddDays(Value: Word): TDateTime;
    function AddHours(Value: Word): TDateTime;
    function AddMilliseconds(Value: Word): TDateTime;
    function AddMinutes(Value: Word): TDateTime;
    function AddMonths(Value: Word): TDateTime;
    function AddSeconds(Value: Word): TDateTime;
    function AddYears(Value: Word): TDateTime;
    function Date: TDateTime;
    function Day: Word;
    function DayOfWeek: TDayOfWeek;
    function DayOfYear: Word;
    function DaysInMonth(const AYear, AMonth: Word): Word;
    function Hour: Word;
    function IsLeapYear(Year: Word): Boolean;
    function Millisecond: Word;
    function Minute: Word;
    function Month: Word;
    function Now: TDateTime;
    function Parse(const S: string): TDateTime; overload;
    function Parse(const S: string; const AFormatSettings: TFormatSettings): TDateTime; overload;
    function Second: Word;
    function Ticks: Int64;
    function TimeOfDay: TDateTime;
    function Today: TDateTime;
    function ToString(const Format: string = ''): string;
    function TryParse(const S: string; out Value: TDateTime): Boolean; overload;
    function TryParse(const S: string; out Value: TDateTime; const AFormatSettings: TFormatSettings): Boolean; overload;
    function Year: Word;
  end;

implementation

{ TDateTimeHelper }

function TDateTimeHelper.AddDays(Value: Word): TDateTime;
begin
  result := System.DateUtils.IncDay(Value);
end;

function TDateTimeHelper.AddHours(Value: Word): TDateTime;
begin
  result := System.DateUtils.IncHour(Value);
end;

function TDateTimeHelper.AddMilliseconds(Value: Word): TDateTime;
begin
  result := System.DateUtils.IncMilliSecond(Value);
end;

function TDateTimeHelper.AddMinutes(Value: Word): TDateTime;
begin
  result := System.DateUtils.IncMinute(Value);
end;

function TDateTimeHelper.AddMonths(Value: Word): TDateTime;
begin
  result := System.SysUtils.IncMonth(Value);
end;

function TDateTimeHelper.AddSeconds(Value: Word): TDateTime;
begin
  result := System.DateUtils.IncSecond(Value);
end;

function TDateTimeHelper.AddYears(Value: Word): TDateTime;
begin
  result := System.DateUtils.IncYear(Value);
end;

function TDateTimeHelper.Date: TDateTime;
begin
  result := System.DateUtils.DateOf(Self);
end;

function TDateTimeHelper.Day: Word;
begin
  result := System.DateUtils.DayOf(Self);
end;

function TDateTimeHelper.DayOfWeek: TDayOfWeek;
begin
  result := TDayOfWeek(System.SysUtils.DayOfWeek(Self) - 1);
end;

function TDateTimeHelper.DayOfYear: Word;
begin
  result := System.DateUtils.DayOfTheYear(Self);
end;

function TDateTimeHelper.DaysInMonth(const AYear, AMonth: Word): Word;
begin
  result := System.DateUtils.DaysInAMonth(AYear, AMonth);
end;

function TDateTimeHelper.Hour: Word;
begin
  result := System.DateUtils.HourOf(Self);
end;

function TDateTimeHelper.IsLeapYear(Year: Word): Boolean;
begin
  result := System.SysUtils.IsLeapYear(Year);
end;

function TDateTimeHelper.Millisecond: Word;
begin
  result := System.DateUtils.MilliSecondOf(Self);
end;

function TDateTimeHelper.Minute: Word;
begin
  result := System.DateUtils.MinuteOf(Self);
end;

function TDateTimeHelper.Month: Word;
begin
  result := System.DateUtils.MonthOf(Self);
end;

function TDateTimeHelper.Now: TDateTime;
begin
  result := System.SysUtils.Now;
end;

function TDateTimeHelper.Parse(const S: string; const AFormatSettings: TFormatSettings): TDateTime;
begin
  result := System.SysUtils.StrToDateTime(S, AFormatSettings);
end;

function TDateTimeHelper.Second: Word;
begin
  result := System.DateUtils.SecondOf(Self);
end;

function TDateTimeHelper.Parse(const S: string): TDateTime;
begin
  result := System.SysUtils.StrToDateTime(S);
end;

function TDateTimeHelper.Ticks: Int64;
begin
  result := System.DateUtils.MilliSecondOf(Self) * 10000;
end;

function TDateTimeHelper.TimeOfDay: TDateTime;
begin
  result := System.DateUtils.TimeOf(Self);
end;

function TDateTimeHelper.Today: TDateTime;
begin
  result := System.DateUtils.Today;
end;

function TDateTimeHelper.ToString(const Format: string): string;
begin
  if Format = '' then
    result := System.SysUtils.DateTimeToStr(Self)
  else
    result := System.SysUtils.FormatDateTime(Format, Self)
end;

function TDateTimeHelper.TryParse(const S: string; out Value: TDateTime;
  const AFormatSettings: TFormatSettings): Boolean;
begin
  result := System.SysUtils.TryStrToDateTime(S, Value, AFormatSettings);
end;

function TDateTimeHelper.TryParse(const S: string; out Value: TDateTime): Boolean;
begin
  result := System.SysUtils.TryStrToDateTime(S, Value);
end;

function TDateTimeHelper.Year: Word;
begin
  result := System.DateUtils.YearOf(Self);
end;

{ TDayOfWeekHelper }

function TDayOfWeekHelper.ToInteger: Integer;
begin
  result := Ord(Self);
end;

end.
```

.NET の [DateTime 構造体](https://msdn.microsoft.com/ja-jp/library/system.datetime(v=vs.110).aspx) を~~パクった~~インスパイアしたレコードヘルパも簡単に作れます。

使い方は uses 句にこのユニットを追加するだけです。

```pascal:
uses
  ..., uDateTime;
```


# おわりに
この XE4 から拡張されたレコードヘルパは列挙型等に対しても使えます。上の uDateTime.pas では TDayOfWeek 列挙型に対するレコードヘルパ TDayOfWeekHelper がありますね。

```pascal
var
  s: string;
begin
  s := Now.DayOfWeek.ToInteger.ToString;
end;
```

このコードは今日の曜日を表す TDayOfWeek を数値に変換したものを文字列に変換しています。今日が日曜日の場合、S には '0' が入ります (6=土曜日)。

従来からのレコードヘルパーも含め、この機能は **Fortran 2003** や **Oberon-2** でいう所の **Type-Bound Procedures** に近い考え方だと思います。

:::note info
Delphi 11.0 Alexandria で、TDatetime 型に対するレコードヘルパ (System.DateUtils.TDateTimeHelper) が追加されました。
:::


**See also:**

 - [プリミティブ型のヘルパー (Delphi Forum)](https://ht-deko.com/delphiforum/?vasthtmlaction=viewtopic&t=1940)