Swanman's Horizon

性欲をもてあましつつなんらかの話をするよ。

for-in-doを使ったC#のusingもどき。

先日書いたような、インターフェースの参照カウントを利用したスマートポインタは便利は便利なんだけど、基本的に参照カウントの変動は(nilなどを明示的に代入しない限り)手続きや関数を抜ける際に行われるので、使い終わったらさっさと解放して欲しいというタイプの人間からすると非常にもどかしい。いや、別に僕はそんな人間じゃないんだけど、そういう人がいるという前提で話を進めないと進まないのでとりあえず進めちゃう。

C#のusingというのは細かい説明はググってもらうとして、Delphiでもそんなのがあったらなーと。でも上に書いたようにインターフェースは手続き・関数単位でしか動いてくれないし、任意の範囲で解放処理を任せられるようなもの…といえば、そういえばfor-in-doのEnumeratorはforブロック抜けると解放された気がする!ということで一発書いてみた。

unit Lyna.Joke;

interface

type
  TForUsing<T: class> = record
  private
    FValue: T;
  public
    class function Create(AValue: T): TForUsing<T>; static;

    type
      TEnumerator = class
      private
        FValue: T;
        FUsed: Boolean;
      public
        constructor Create(AValue: T);
        destructor Destroy; override;
        function MoveNext: Boolean; inline;
        property Current: T read FValue;
      end;

    function GetEnumerator: TEnumerator;
  end;

implementation

{ TForUsing<T> }

class function TForUsing<T>.Create(AValue: T): TForUsing<T>;
begin
  Result.FValue := AValue;
end;

function TForUsing<T>.GetEnumerator: TEnumerator;
begin
  Result := TEnumerator.Create(FValue);
end;

{ TForUsing<T>.TEnumerator }

constructor TForUsing<T>.TEnumerator.Create(AValue: T);
begin
  inherited Create;
  FValue := AValue;
  FUsed := False;
end;

destructor TForUsing<T>.TEnumerator.Destroy;
begin
  FValue.Free;
  inherited;
end;

function TForUsing<T>.TEnumerator.MoveNext: Boolean;
begin
  Result := not FUsed;
  FUsed := True;
end;

end.

// 使い方
type
  TTestObject = class
  public
    constructor Create;
    destructor Destroy; override;
  end;

constructor TTestObject.Create;
begin
  Writeln('Create');
end;

destructor TTestObject.Destroy;
begin
  Writeln('Destroy');
end;

procedure ForUsingTest;
var
  obj: TTestObject;
begin
  Writeln('はじまるよー');
  for obj in TForUsing<TTestObject>.Create(TTestObject.Create) do
  begin
    Writeln(obj.ClassName);
  end;
  Writeln('おわったよー');
end;

// 出力結果
// > はじまるよー
// > Create
// > TTestObject
// > Destroy
// > おわったよー

GetEnumeratorという関数やそれで返されるTEnumeratorクラス(のCurrentやMoveNext)はfor-in-do機構を作るためにコンパイラに特別視されていて、内部で例外が起きてもちゃんと解放されるように暗黙のtry-finallyが付加される。なのでTEnumerator.Destroyは確実に動作するから、そこに余分な解放処理書いちゃえ的な。あとは型パラメータにconstructor制約を付けて、引数無しのCreateをオーバーロードして作って、GetCurrentを作るかMoveNext内でFValueにT.Createする処理(要するに先日のスマートポインタと同じ処理)を入れると多少実用に近付く…かも?まぁ曲芸に変わりはないけど。