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する処理(要するに先日のスマートポインタと同じ処理)を入れると多少実用に近付く…かも?まぁ曲芸に変わりはないけど。