バージョン管理システム

複数の開発者が作成したコードをマージしたり、仕様が一部異なる複数のプロジェクトに共通の変更を加えたりするときに、手間なく正確に作業するにはバージョン管理システムが役に立ちます。数年前からバージョン管理システムのSubversion(TortoiseSVN)を使ってきましたが、今回Mercirual(TortoiseHg)を試しました。

リポジトリ・コミット・マージ

変更履歴を記録してあるデータストアをリポジトリと呼びます。コミットでその時点のスナップショットをリポジトリに保存します。このときリビジョンが刻まれ、必要があれば簡単に任意のリビジョンに戻すことができます。

バージョン管理で重要な機能がマージです。マージは複数箇所での変更を1つのまとめる機能です。異なる開発者の変更をまとめるときや、あるプロジェクトに加えた変更を仕様が一部異なる別のプロジェクトに適用するときにマージが発生します。ファイルをプロジェクトに追加しても自動的にマージしてくれるのはもちろんのこと、同じファイルを変更した場合にも変更を加えた行が隣り合っていなければ自動でマージしてくれます。隣り合っているか同一行を変更した場合は競合が発生し、競合解消用のエディタによって手動でマージします。

集中型・分散型

Subversionはリポジトリを1カ所で持つ集中型と呼ばれる物で、それに対してMercirualはリポジトリを複数持つことができる分散型と呼ばれる物です。開発者それぞれがコーディングのスクラップアンドビルドするときに自分用のリポジトリを利用すれば他の開発者に影響を与えません。これが分散型のメリットです。Mercurialの他にGit、Bazaarなどがあります。

当方は開発規模が小さいので集中型のSubversionでも十分なのですが、Mercurialに備わっているRebase, Transplant, Splitが仕様が一部異なる複数のプロジェクトを管理するのに便利に使えることがわかりました。

ブランチ

一部仕様の異なる複数のプロジェクトはブランチという機能を使って管理します。メインで開発するブランチをSubversionではtrunkという名前を付ける習慣になっているようです。trunkでの変更を別のブランチにも適用するとき、Subversionではリビジョンの範囲をマージという機能を使います。更新→マージ→動作確認→コミットの順で操作します。どこまでのリビジョンをブランチに適用したかSubversionが把握してくれているので、いちいちマージするリビジョンを指定する必要はありません。ブランチで変更した結果をtrunkに反映する際にもリビジョンの範囲をマージを使います。但し、相互にマージする場合は、相手方のブランチでの変更だけをマージするためリビジョンの範囲を明示的に指定します。マージ結果をコミットしたリビジョンまでマージしてしまうと同じ更新を2度コミットすることになり、競合が発生します。

リビジョンの範囲をマージを使うと2つのブランチがそれぞれの更新を他方のブランチにマージする形でリビジョンが成長していきます。

Mercurial

MercurialのRebaseはこれとはことなりブランチの分岐点を変えることによって共通の変更を他のブランチに適用します。つまりdefault(Subversionのtrunkに相当)とブランチの差違を最後に適用したことにできるのです。逆にブランチに加えた変更をdefaultに適用するときはTransplantによってdefaultに適用したことにして、Splitによってブランチに適用した変更を取り消し、Rebaseします。

リビジョンの成長過程はリビジョングラフによって確認できますが、リビジョングラフがシンプルでわかりやすいものになり、私はこの方法が気に入りました。

Mercurialは日本語ファイル名の扱いに難があり、win32mbcsエクステンションを使用し、一部のDLLを入れ替えなければなりません。

私は下記URLを参考に設定しました。

http://www.asukaze.net/etc/vcs/hg-fixutf8.html

しかし、Transplant実行時に日本語ファイル名が原因でエラーが出てしまいました。当初想定した使い方ができないので今のところSubVersionを継続使用するつもりです。

mercurial 2.0 では上記の不具合が解消されているようです。win32mbcsエクステンションは必要ですがDLLの入れ替えは不要になっているようです。時間を掛けて検証していないので見落としがある可能性があります。次回新たなプロジェクトが立ち上がったら使ってみます。

Git

久しぶりに見返しましたが、この記事を書いたときから状況が変わりました。Visual Studio にも最初から Git がついています。現在ではもっぱら Git を使用しています。

Windows Forms でのアーキテクチャー

当方がデータベースアプリケーションをWindows Formsで作成するときのアーキテクチャーを紹介します。

レイヤードアーキテクチャー

全体としてはModelとViewを分離するDocument-Viewを使用しています。Viewの役割を表示とキーボード・マウスなどのUIイベント処理に限定すると見通しが良くなります。それに伴い画面遷移などをModel側で処理するようになるとModelの役割が多くなり見通しが悪くなってきましたので、Model内の構造を見直す必要がでてきました。

そこでレイヤードアーキテクチャーを採用してModel内を分割することにしました。アプリケーション層・ドメイン層・データアクセス層に分割しています。アプリケーション層とドメイン層の間にサービス層を追加していたときもありましたが、ユーザの確認を差し挟む処理が書きにくいのでサービス層を無くしました。

コンポーネント 要約 コマンド実行 データバインディング その他 Reflectionでの分類
View データ表示、UIイベントの処理
  • UIイベントを受けてコマンド呼び出し。
  • ほとんどのコマンドはModelのメソッドを呼び出す。
アプリケーション層が提供するデータソースを入力・表示部品にバインディング。
  • Modelからの通知を受けて、画面遷移先のViewを生成
  • ボタン使用可・不可の連動、ダイアログの表示
  • その他UIデバイスの更新
メタレベル
Model アプリケーション層 (プレゼンテーションモデル) Viewの状態管理、コマンドの実行、ドメインの集約
  • Viewから呼び出されるコマンドに対応する処理を実行。
  • データ操作を伴わない画面遷移等はアプリケーション層で完結。
  • データ操作を伴うコマンドの具体的な処理はドメイン層に委譲。
  • 削除確認等処理に伴うダイアログ表示はアプリケーション層からViewへの通知。
  • 編集画面ではエンティティの変更追跡(Unit of Work)、トランザクションの管理を行う
データバインディングに必要なエンティティを集約し、データソース取得メソッドを通じて公開。 
  • 新しいModelのインスタンスを作成することで間接的に画面遷移。
  • Viewで使用するボタンの使用可・不可もここで管理
  • Viewへの各種通知
  • 画面毎にインスタンス生成
-
ドメイン層 (ビジネスロジック層) エンティティおよびビジネスロジックの提供
  • ドメインモデルおよびテーブルモジュールでロジックを記述。ルートエンティティのリストを集計する処理はテーブルモジュール
  • リポジトリにクエリーを記述。HQLのような抽象化はしていない。
  • 年次更新・インポート・エクスポートなどのバッチ処理、SQL使用
  • エンティティおよび所有する値オブジェクトがデータバインディングの対象となる。
  • アイデンティティ・マッピングを採用
  • エンティティ・値オブジェクトはコード生成。
  • マスタ類は全件読み込みしてリポジトリにキャッシュ。伝票類は画面毎にその都度インスタンス生成。
ベースレベル
データアクセス層 (パーシステンス層) データベースとの入出力
  • テーブルデータゲートウェイ形式でデータアクセス。
  • アクセス先の決定
  • DAOは不採用
  • 大方の機能はライブラリ化されているので記述量は少ない
メタレベル

レイヤー間の連係

各レイヤー間の連係は下図のようにしています。

データバインディングではViewからドメイン層のエンティティに直接バインディングしています。

コマンド実行では各層で必要があれば下層を呼び出しています。

各層は、Reflectionパターンのベースレベルまたはメタレベルで記述します。ドメイン層ではベースレベルで記述、すなわちエンティティのプロパティをそのまま使用します。Viewやデータアクセス層はメタレベルで記述、Viewでは入力部品やDataGridViewColumnがプロパティを表します。データアクセス層ではカラムオブジェクトがプロパティを表します。

データのロードはリポジトリを通じて行います。データベースの更新はUnit of Work によって行います。XMLやテキストファイルへの保存はリポジトリを通じて行います。

リポジトリにはクエリーを記述しメソッドによって呼び出せるようにします。伝票-伝票明細のような構造を持つ場合、関係表構造(IDataReader)からオブジェクトグラフへの変換(R/O変換)は汎用ライブラリを利用してリポジトリ内で行っています。

画面の継承関係

画面はメニュー・一覧・編集といった風にある程度パターン化しています。それぞれパターンには共通の処理がありますのでこれらをベースクラスに記述し、具象画面は派生クラスに記述します。Viewではもっぱらデザインでの継承階層、アプリケーション層ではメニュー・一覧・編集といった機能による継承階層を持たせています。

これによって各具象画面のコードは少なくなります。単純なCRUDだけですとView側に自分で記述するコードはありません。アプリケーション層もほとんど記述はありません。

画面遷移

画面遷移はModel側のアプリケーション層で管理します。ViewはModelに追従して画面遷移を行います。

下図でCallerModelは遷移元Model、CalllerFormは遷移元View、BaseModelはModelの共通の基本クラス(ここではstaticメソッドを表現)、CalleeModelは遷移先Model、CalleeFormは遷移先Viewです。

遷移元Modelが遷移先Modelの型とオプションを指定してstaticメソッドOpenModelを呼び、それに従ってCalleeModelを作成・初期化します。既に作成済みであれば再利用します。作成済みModelはグローバルなDictionaryによって管理しています。コンストラクタと初期化メソッドを分けているのはメソッドのオーバーライドによって初期化処理を柔軟に実装したいからです。

初期化が終わると、遷移先ModelのCallerプロパティに遷移元Modelを設定し、遷移先Modelでは遷移元ModelのAddCalleeを呼び出します。これを受けて遷移元Modelでは対応する遷移元Viewに対して、CallerAddedによって遷移先Modelが追加されたことを通知します。遷移元Viewは追加された遷移先Modelに対応する遷移先Viewを生成します。作成するViewは一定の規則でModel名からView名を導き出してリフレクションによって作成します。以上で遷移先ModelとViewの作成が完了し、次に遷移先ModelのRunメソッドを実行してModelとしての最初の動作を実行します。通常は対応するViewに対してShowを通知し、表示を促します。

このシーケンスですと画面間のデータの受け渡しは、Model同士で直接行いますのでViewを通じて受け渡すよりもシンプルになります。

参照

対話型システムのアーキテクチャー(MVCの仲間達)

対話型システムのアーキテクチャーとして代表的なものにMVCがあります。そしてMVC以外にもさまざまなアーキテクチャーがあります。それらのアーキテクチャー間で用語が違っているなど理解しにくかったので、自分が理解している範囲でまとめてみました。仲間達と書きましたが全く異なる視点で設計されているものもあります。

MVC(Model-View-Control)
アプリケーションをControl(入力)-Model(処理)-View(出力)に分割する。Modelをデータと捉えるとControlにビジネスロジックを書くことになりうまく分割できない。命名が誤解しやすい。データバインディング・Observerパターンを使うとModelからViewの更新はControlの補助無しに行われる。Webのサーバ側アプリケーションやWindows Formsでもデータバインディング・Observerパターンを使えない処理ではControlがViewを呼び出す。
Document-View
MVCのMがDocument、VCがViewに相当する。Windows Forms 等ではキーボード・マウス入力の大半を自動で処理してくれるので、独立したControlが必要になることはあまりない。そのようなUIではMVCのVCが一体化したDocument-Viewが向いていると思われる。
PAC(Presentation-Abstraction-Control)
アプリ-ケーションを複数のエージェントに分けて管理する。1つのエージェントはPresentation(入出力)-Control(処理)-Abstraction(データ)からなる。エージェント同士の独立性を保つことを重視していると思われる。エージェント間はControlを通じて細いインターフェースでやりとりする。プラグインを実装する、異なるプロセスで協調動作する、異なるマシンで協調動作するなどの用途に向いていると思われる。
BCE(Boundary-Control-Entity)
アプリケーション開発のロバストネス分析過程で用いられる。Boundary(入出力)-Control(処理)-Entity(データ)に分割する。処理をどのEntityに持たせるべきかを検討する場合にも用いられるので、できあがったソフトウェアの構造がBCEに分離しているわけではない。
MVVM(Model-View-ViewModel)
WPF、Silverlightで用いられている。View(入出力)-ViewModel(View用の処理)-Model(Viewに特化しない処理)。
アーキテクチャー対比表
MVC Document-View PAC BCE MVVM
1 Control View View Presentation Boundary View
2 Model Document Control Control ViewModel
3 Model
4 Abstraction Entity

Windows Formsの開発ではDocument-Viewを使っています。View上のボタン使用可/不可の状態はModelで管理し、ObserverパターンでViewを更新しています。画面遷移はModelが遷移先のModelを作成することで行っています。レイヤーアーキテクチャーによってModelはさらに複数の層に分けています。

PHPによるWebアプリケーションではMVCを使っています。Controlはページ毎に持っていて、ページへの要求の解釈、Modelの呼び出し、ビューの呼び出しを行っています。Modelは各種更新処理、表示用のデータロードを行っています。Viewではテンプレートにデータを渡して、HTMLを組み立てます。

どちらにしてもModelにはViewへの参照は持たないもののViewを意識した処理を行っています。

ViewとModelを分離しなかった場合、同じロジックを複数の画面で使いたい場合に、ロジックが入力部品と結びついているため再利用が困難です。最小限ViewとModelを分離するとこのようなことが防げます。

これらのアーキテクチャーを採用することで、アプリケーションに秩序が生まれ、どこに何が書いてあるかわかりやすく、生産性・保守性が上がる点がメリットであると考えています。

参照

C# dynamicの使いどころ

Visual Studio 2010ではC#にdynamicが追加されました。

JavaScript, php, rubyの様に実行時に型を解決しメソッドをバインド(レイトバインディング)するものです。Visual Basicでは以前からソース毎にレイトバインディングすることを指定できましたが、c#のdynamicでは変数毎に指定できるのです。

現状のC#の言語仕様では残念ながら型のつじつま合わせのためのコードを書かなければなりません。これを部分的に軽減するためにdynamicが使えます。

オープンジェネリックメソッド呼び出し

例えば、型引数を持ったクラスBase<T>のメソッドMethod1を他のクラスから呼び出そうとしても、型引数が決まらない(オープンジェネリック型)ためにキャストできないので呼び出せないのです。オープンジェネリック型のメソッドを呼び出そうとする場合には、リフレクションを使用する方法の他に、型引数を持たないBase<T>の基底クラスBaseを作成してMethod1を仮想メソッドとして持たせ間接的にBase<T>Method1を呼び出す方法があります。Netframeworkにもこのパターンはよく現れます。恐らくVisual Studio 2008までの場合は、この方法がベストプラクティスと思われます。この方法の欠点は型のつじつま合わせのためだけに2つのクラスを作らなければならないことです。そしてコーディング量が大変増えてしまいます。

オープンジェネリック型変数の宣言にdynamicを使えば基底クラスを作らなくてもメソッド呼び出しができるようになります。欠点としてはコンパイル時型チェックが効かなくなることです。

今後C#の言語仕様が改善されて共変・反変の機能が強力にサポートされ、dynamicを使わなくてもオープンジェネリック型メソッドを呼ぶことができるようになることを期待します。

Visitorパターン代替

Visitorパターンでは、Visitorが、継承されたそれぞれのAcceptorのAcceptメソッドを呼ぶと、Acceptorの型に応じたVisitor.Visitメソッドを呼び返します。この仕組みをdynamicで実現すれば、AcceptorのAcceptを経由しないでディスパッチできます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class BaseItem
{
}

class Leaf : BaseItem
{
}

class Node : BaseItem
{
public List<BaseItem> Branch { get; private set; }

public Node()
{
Branch = new List<BaseItem>();
}
}

class Visitor
{
public void Visit(Leaf item)
{
//何らかの処理
}

public void Visit(Node item)
{
//何らかの処理
foreach (var child in item.Branch) {
CallVisit(child);
}
}

public void CallVisit(dynamic item)
{
Visit(item);
}
}

注意点

dynamicで宣言された変数を通してメソッドを呼ぶときに引数に変数を指定した場合、変数宣言の型でメソッドがディスパッチされるか、変数に格納された値の型でディスパッチされる値の型でディスパッチされるか配慮しなければならないことがあります。

1
2
3
dynamic callee = new Callee();
object arg = "TestString";
callee.Method(arg);

例えば上の例ではメソッドに渡される引数はobjectで宣言されていますが、変数に代入されている値はstringです。この場合、void Method(string param)void Method(object param)のどちらが呼び出されるかと言うことです。

答えは変数宣言の型でメソッドがディスパッチされるです。この場合はvoid Method(object param)が呼び出されます。これを間違いvoid Method(object param)を用意しなければ最も適しているオーバーロード メソッドには無効な引数がいくつか含まれていますとのエラーが発生します。

変数に格納された値の型でディスパッチして欲しい場合は変数宣言もdynamicにします。

1
2
3
dynamic callee = new Callee();
dynamic arg = "TestString";
callee.Method(arg);

PHP

従来はWeb開発でrubyを積極的に使ってきました。PHPに最初に触れた頃にはオブジェクト指向でプログラミングできませんでしたので、当時はあまり魅力のない言語と認識していました。去年からPHPを使用されてきたSI様とのおつきあいが始まったので、PHPに積極的に取り組むことになりました。バージョンはPHP 5.2.14です。このバージョンになるとオブジェクト指向で組むのに困らないほどに一通りの機能が揃っています。さらに5.3.Xではクロージャの機能が加わり、着実に機能改善されているようです。

PHPの印象は堅実で飛び道具が少ないことです。JavaScriptやrubyでは組み込みクラスにメンバーを追加できますが、PHPではそのようなことはできません。ライブラリは最初から幅広い機能が含まれていて大抵のことに困らないのも良いところです。データベースのNumber型に相当する型(二進化十進)が組み込みでないのが障害になるかも知れません。

以前から有る参照の機能は変数のエイリアスとして使え、便利なものと思います。オブジェクトの代入はruby, C#などの言語と同様に参照の代入になりましたが変数の参照とは意味が違うので少し誤解しやすいところです。配列の代入は従来通り内容のコピーになります。といっても実際には内容を変更したときに初めてコピーされる(コピーオンライト)ので、単純な代入でパフォーマンスが落ちることはありません。

テンプレートエンジンやO/Rマッパーなどrubyで構築したライブラリを移植しましたので、rubyと同様開発を効率化できるものと考えています。

NetBeans

今までPHPを使った開発では、秀丸を使用してきました。秀丸などのテキストエディタでは補完精度が劣る・デバッガないなどIDEに比べると機能的に不便です。そこでEclipseをインストールして使っては見るのですが、動作が遅くタイピングに追いつかないので継続的に使うことはありませんでした。

去年の秋頃からNetBeans6.9.1を使うようになりました。動作の速さは秀丸に比べると劣りますがEclipseと比べればきびきびと動いてくれました。特に便利と思ったことは、

状況に応じた識別子の補完、オブジェクト指向で組んでいると補完候補がクラスのメンバーに絞られるので間違いが少なくなる

  • PHPDoc・JsDocなどのドキュメントの雛形を作成してくれ、関数・クラスなどを使用した箇所でドキュメントがポップアップしてくれる
  • 関数・クラスなどの定義にジャンプできる
  • リモートデバッグできる
  • リファクタリング支援機能が備わっている
  • 単体テストが統合されている
  • バージョン管理システムと連係できる
  • コードテンプレート(スニペット)が使える

などです。識別子の補完では動的型付け言語にもかかわらず、たいていの場合において正確に補完してくれました。リモートデバッグは今まで使ったことがありませんでした。Web開発では処理途中の変数の内容を調べるためにvar_dumpを挿入するなどで対応していましたが、リモートデバッグを使えばソースに変更を加えることなくデバッグできますので効率が上がります。

少し不便なので設定を変えて使っているところがあります。それは自動ポップアップを無効にしていることです。下記URLに報告しましたが、自動ポップアップを有効にしておくとコードテンプレートが使えなくなってしまうのです。自動ポップアップを無効にしても<Ctrl>-<Space>を押せば呼び出せるのでたいした不便はありません。

JavaScriptではコード補完が不正確だったり、ソース整形が不正確だったりでPHP対応に比べて劣っています。

NetBeansに慣れると秀丸に戻れなくなりました。

参照

C#でnullチェック

1
2
3
4
5
6
7
8
9
public Control GetParentParent(Control control)
{
if (control != null) {
if (control.Parent != null) {
return control.Parent.Parent;
}
}
return null;
}

インスタンスメソッドを呼び出すときに気をつけないといけないことのひとつにオブジェクトがnullかどうかをチェックしなければならないことがあります。チェックを怠るとNullReferenceExceptionが発生してしまいます。しかし、nullチェックをするとコード行数が増え、見通しが悪くなります。

例えばコントロールの親の親を取得するメソッドGetParentParentはおおよそ右のコードになります。親の親に該当するものがなければnullを返します。オブジェクトをたくさんたどればそれだけ条件分岐が増えていきます。

1
2
3
4
5
6
7
8
9
public static void _<T>(this T obj, Action<T> action)
{
if (obj != null) action(obj);
}

public static TResult _<TSource, TResult>(this TSource obj, Func<TSource, TResult> func)
{
return obj == null ? default(TResult) : func(obj);
}

これを簡単に書くためにちょっとしたヘルパーメソッドを作成します。コードを短くするために名前をアンダースコアにしてあります。次の1つめの拡張メソッドはobjがnullの場合actionを実行しません。ジェネリックを使っていますのでactionとつじつまが合っていればどんな型でも渡せます。2つめのメソッドは値を返すものでobjがnullの場合funcを実行しないで返値の型TResultの既定値を返します。TResultがValueTypeでなければnullを返します。インスタンスメソッドの場合はオブジェクトがnullの場合にメソッド呼び出しができませんが、拡張メソッドは静的メソッドを第1引数のメソッド呼び出し形式で呼び出せるシンタックスシュガーに過ぎませんので引数objにnullが渡されてもエラーにならないのです。

1
2
3
4
public Control GetParentParent(Control control)
{
return control._(c => c.Parent)._(c => c.Parent);
}

このヘルパーメソッドを利用するとGetParentParentはLINQ同様メソッドチェーン1行で記述できます。

HaskellにはMaybeモナド、Delphi Prismにはコロン演算子がありますが、C#でも上記のヘルパーメソッドで簡潔に書けるようになります。

Visual Basicではアンダースコアが行連結文字になっていますので別の名前を付けなければなりません。

1
2
3
4
public Control GetParentParent(Control control)
{
return control?.Parent?.Parent;
}

C# 6.0 (Visual Studio 2015) では Null条件演算子(?.)が導入されました。

参照

Visual BasicからC#に移行した結果

1つのプロジェクトをVisual BasicからC#に移行しました。実際移行してみて感じたメリット・デメリットがありました。

C#の方が優れていた部分として、

ラムダ式によってAdapterが不要になった
私はコマンドを受け付ける部分をコマンドパターンによって使い回しするようにしています。Visual Basicではルーチンをコマンドにラップするクラスを記述していました。ラムダ式によって簡単にコマンド化することができるのでこれら多くのクラスが不要になりました。コーディングの自由度が高く、SEの設計センスがより問われると思います。
コンパイル速度が速い
体感で2倍程度。
リファクタリングの種類が豊富
名前の変更だけでなく、フィールドのカプセル化・パラメータの順序変更など多くのリファクタリングが利用できる。

C#の使いにくい部分は、

括弧の対応が分かりにくい
if,swithなどの制御構造を追加するなどインデントが崩れると右括弧がどれに対応しているのかわからなくなる。End Subなどでメリハリが付いているVisual Basicの方がよい。
型名が省略できない
Withもなくusingに型名が指定ないので、他のオブジェクトのメソッドを呼ぶときは型名(or オブジェクト変数名).メソッド名の形式で呼ばなければならず、細かいところで記述量が多い。コンパイル速度が速い原因は型名を省略できないことかも知れないので善し悪しは微妙なところ。

また、null(Nothing)の扱いには違いがあり、Visual Basicではイコールで比較すると長さ0の文字列とNothingを同一視しますがC#では区別します。

総合的にはC#に移行して良かったと思います。その一番大きなファクターはラムダ式です。

参照

Linqと遅延実行のイメージ

Linqと遅延実行のイメージ

Linqでは要素を取得する処理は本当にその要素が必要なってはじめて実行されます。これを遅延実行と呼びます。Linqによる遅延実行は、複数のデバイスが通信によってデータを要求・応答している様子をイメージをすればよいと思います。

Linqと遅延実行のイメージ

デバイス1がデータを発生します。デバイス2がデータを中間処理します。デバイス3がデータを最終処理します。処理はデバイス3がデバイス2に対してデータを要求することで開始します。デバイス2は要求を受けてデバイス1に対してデータを要求します。デバイス1はデバイス2に対して応答を返します。デバイス2は応答を受けたらデバイス3に応答を返します。これが繰り返されるのです。

デバイス1やデバイス2は、図中自分より右のデバイスにデータを要求されて初めて応答します。遅延実行はこのような仕組みで働きます。

遅延実行で省メモリ

リスト1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LinqLazy
{
static class EnumerableExtentions
{
public static void Each<T>(this IEnumerable<T> source, Action<T> func)
{
foreach (var elm in source) {
func(elm);
}
}
}

class Program
{

public static IEnumerable<int> Generator()
{
for(var i=0; i<10000; i++){
yield return i;
}
}

static void Main(string[] args)
{
Generator().Where(x => x > 2).Each(i => Console.WriteLine(i));
Console.ReadKey();
}
}
}
Linqと遅延実行のイメージ

リスト1の例はGeneratorが0から10000までの整数を発生させます。WhereGeneratorから渡された要素が2より大きいものだけを通すようにフィルタリングします。Each内で結果を表示します。遅延実行の様子はシーケンス図で確認してください。MoveNext Currentがデータの要求、yield returnが応答です。

もしGeneratorのすべての処理が終わってからWhereの処理に移るならば、int約10,000個分のバッファが必要になります。しかし遅延実行を行っているために1個分のバッファで済むのです。この場合遅延実行のメリットは省メモリです。

遅延実行で無限Generator

リスト2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LinqLazy
{
static class EnumerableExtentions
{
public static void Each<T>(this IEnumerable<T> source, Action<T> func)
{
foreach (var elm in source) {
func(elm);
}
}
}

class Program
{
public static IEnumerable<int> Generator()
{
//無限に整数をカウントアップする
for(var i=0; ; i++){
yield return i;
}
}

static void Main(string[] args)
{
Generator().TakeWhile(x => x < 3).Each(i => Console.WriteLine(i));
Console.ReadKey();
}
}
}

リスト2ではGeneratorが無限に整数を発生させています。TakeWhileGeneratorから渡された要素が3より小さい間だけ通すようにフィルタリングします。無限ループにならずに終了するのは0,1,2,3だけが処理されるからです。無限ループにならずに実行できるのは遅延実行のおかげです。

TakeWhileWhereに変えると遅延実行はされるものの処理は続き無限ループになります。無限に要素を返すジェネレータはTakeWhileのように処理を打ち切るフィルタと組み合わせて使う必要があります。

遅延実行されない場合

Linqがいつも遅延実行されるわけではありません。Max,Min,OrderByなどは原理的にすべての要素が揃わないと最初の要素すら決まりませんのでこのようなクエリ演算子は遅延実行されません(マイクロソフトでは集中評価と呼んでいるようです)。

Linqで言う遅延実行はメソッド呼び出し時の引数の遅延評価とは意味が異なります。Linqに限らず引数にデリゲート・ラムダ式を指定して評価を呼び出し後に行うことを遅延評価と呼んでいます。遅延実行のためには遅延評価も必要です。

Delphi 2009

Delphi 2009が発売されました。

NetFrameworkではなくWindowsネイティブ用です。2007までと大きく異なりVCLのUnicode化、ジェネリック・無名メソッドの導入と久々の意欲作となっています。

特にUnicodeについてはよく使われるようになったので、これが今まで扱えなかったことはDelphiを使う上で大きな足かせになっていました。

Visual Studio 2008に比べ動作はきびきびしています。Visual Studio+NetFrameworkは開発・実行共に動作がところどころ遅いのですが、Delphiではそのようなことを感じさせません。

過去にDelphiで作ったコレクションを扱うライブラリを早速ジェネリックに置き換えるとコーディング量が1/3程度に減った上に型安全になりました。但しジェネリックに関してはIDEがまだまだブラシアップできておらずエラーではないのにエラーインサイトでエラーを報告するなど不具合が残っています。今後ホットフィックスで修正されるのを待つことになりそうです。

IDEではユニットテスト機能(DUnit)が統合されています。テスト対象クラスから自動的にテストスケルトンを作ってくれます。テスト内容はもちろん自分で記述しなければなりません。

コントロールの貼り付けは非常にスムーズで型名で検索の機能を使うとパレットを探し回る必要がありません。フォームデザイナとコードエディタの切り替えも一瞬です。

言語仕様を比較してみました。

項目 Delphi 2009 VB 2008 C# 3.0 コメント
ジェネリック DelphiはIDEに不具合が残る
無名メソッド × C#ではラムダ式で簡易記述できる
ミックスイン × × DelphiはImplements句によるミックスイン、Rubyと比べて記述は煩雑
コルーチン × DelphiはFiberで対応、言語仕様に機能はない。C#はyieldで列挙に限りコルーチンを使える。
ガーベジコレクション × Delphiでは文字列・インタフェースは参照カウント方式、Componentは親を解放すると所有される子が解放される。それ以外は明示的に解放。
型推論 ×
仮想コンストラクタ × × Abstract Factoryを単純に記述できる
拡張メソッド Delphiはクラスヘルパーを使用、制限大
遅延バインディング × DelphiではVariantを使う。