連載:[完全版]究極のC#プログラミング[補遺]ラムダ式を使用した事例川俣 晶2010/06/07 |
Page1
Page2
|
同書籍は、もともと本フォーラムにて連載していた『C# 2.0入門』、『C# 3.0入門』の記事を整理統合し、加筆、修正されたものです。 手元でまとめて読みたい方は、ぜひ書店などにてお買い求めください。 【注意】本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。 |
本書ではラムダ式について解説しているが、実例があったほうがわかりやすいと思うので、実際に筆者が書いたラムダ式を使用したコードの事例を紹介しよう。
それは、ゲームなどでよく使われる「フラグ」と呼ばれる機能のソースコード体質改善(リファクタリング)を行う際に体験したことである。ここでいうフラグとは、文字列からなる名前とそれに関連付けられた整数の集まりである。
なお、以下に紹介するソースコードはわかりやすさを優先して基本機能以外を除去し、若干の修正を加えていることをお断りしておく(つまり、実際にチェックインしたコードと同じではない)。
さて、単に文字列をキーに整数を保管するだけなら、誰でも思い付くとおり、次のような実装でよい。
|
しかし、裸のままのコレクションを自由に触らせるのは好ましいことではないので、フラグの値を設定/取得するGetFlag/SetFlagメソッドをリストS.1のとおり作成した。
| |
リストS.1 Dictionary版フラグ用メソッド |
ちなみに、「登録されていないフラグ名に対しては0を返す」、「誤った名前で処理が進行することは好ましくないので、必ずフラグ名をチェックする」という2つの条件を付加している。ValidateFlagNameメソッドは、フラグ名をチェックして未定義の名前ではない場合は例外を投げる。
さて、このコードは十分にシンプルであるが、ソースコードの体質改善作業に着手した時点で次の2つの問題が浮かび上がった。
-
フラグ名のチェックが実行時まで遅延されるとバグが早期に発見できない。コンパイル時にフラグ名をチェックできないだろうか?
-
プロファイラにかけたところ、ValidateFlagNameメソッドの処理が遅いことがわかった。このため自動テストに時間がかかりすぎていてテストの実行頻度が落ちている。品質改善のために自動テストを高速化したい
さて、この問題を解決するにはどうしたらよいだろうか?
最初に考えた解決案は、フラグ名をフィールド名に置き換えた次のリストS.2のようなクラスを作成することであった。フィールドを直接、無制限かつ自由に外部から操作可能にすることは気持ちが悪いと思う読者もいると思うが、もともとここで扱うフラグはプログラム中の任意の箇所から自由に読み書きを許すものなので、機能的には要求と実装は合致する(さらにいえば、これらは必要があればプロパティに置換可能であり、実際の実装では一部がプロパティとして実装されている)。
| |
リストS.2 フィールド版フラグ |
これで、「正しいフラグ名を使用していることがコンパイル時に検証される」、「コンパイル時に名前が検証されるので、実行時に重いValidateFlagNameメソッドを呼ぶ必要がない」という2つの成果を得られる。ちなみに、フラグはファイルに対して保存/読み込みの機能を持つ必要があるが、それはクラスのシリアライズと考えれば、なんら難しい話ではない。リフレクション機能を使って、フィールドの名前と値のペアを読み取る/値を書き込む処理を記述すれば容易に実現できる。
しかし、これでハッピーエンドとはならなかった。次のような問題が出たからである。
- アプリケーションの難読化を実行すると、フィールド名が変更され、変更された名前がつねに同じとは限らない。つまり、ファイルに書き出した情報を読み込むと、同じフラグに値を読み込めない可能性がある
- フラグ名を文字列処理で生成したいケースがある。たとえば、「機能名+番号」がフラグ名になるケースでは、文字列結合でフラグ名を得る必要がある。このようなケースでは、文字列をもとにフラグを読み書きする「GetFlag(名前)」や「SetFlag(名前, 値)」というメソッドを用意せざるをえない
はじめの名前の問題は本筋ではないので解決策を簡単に説明しよう。これは、次のリストS.3のように、属性で名前を添えることで解決した。フラグ名の互換性が意味を持つのはプログラム外部とのやり取りだけなので、属性で本来の名前を添えておけば十分である。ちなみに、属性で名前を添えるという解決策は、C#がフィールド名に許していない文字を使ったフラグ名対策としても有効である。
| |
リストS.3 属性によりフラグ名をフィールドに添付 |
さて、上記の2番目の問題であるが、文字列でフラグ名を受け取ってフィールドを読み書きすること自体は、リフレクションを使えば難しい話ではない。しかし、プロファイラで実際に測定してみると、リフレクションは予想以上に重い処理だった。このソースコードの体質改善では「自動テストの高速化」というテーマを掲げているので、速度が遅いやり方はNGである。
具体的にリフレクションの何が遅いのかというと、名前を与えて目的のフィールドを探し出す処理である。ならば、ある特定の名前のフィールドを読み書きするラムダ式を実行開始時点で用意しておき、事前にそれをフラグ名と結び付けておけば素早くフラグを読み書きできることになる。
実際に、この意図を実装したのが次のリストS.4に示したクラスである。
| |
リストS.4 マップによりフィールドとラムダ式を関連付けたFlagsListクラス |
効率という観点からいえば、おそらくラムダ式を使わず、フラグ名と変数infoの値を関連付けるマップを1つ作成するだけでも十分だろう。そのほうがメモリ消費量も少ない。しかし、実際にはゲッター(読み出し)とセッター(書き込み)の2つのマップを作成している。このコードの真価は、フラグの型のバリエーションを増やした時点で発揮される。
たとえば、0か1しかありえないフラグはint型よりもbool型で表現するほうがよいとして、次のようなフィールドも許すことにしたとしよう。
|
これに対処して、FlagsListクラスのコンストラクタは次のように修正できる。なお、GetValue/SetValueメソッドはフィールドの値を読み書きするためのFieldInfoクラスのメソッドである。
|
|
これにより、GetFlag/SetFlagメソッド経由のアクセスは従来どおりint型の値で行うが、フィールドに直接アクセスする場合はfalse/trueで読み書きできるようになった。
もし、フラグ名と変数infoの値を関連付けるマップを作成しているとすれば、GetFlagメソッドの実装内容は次のリストS.5のようになるだろう。
| |
リストS.5 GetFlagメソッドの内容(フラグ名と変数infoを関連付けた場合)(抜粋) |
しかし、フラグ名とラムダ式を関連付けたマップを作成していれば、次ページのリストS.6の内容でよい。
| |
リストS.6 GetFlagメソッドの内容(フラグ名とラムダ式を関連付けた場合)(抜粋) |
両者の差は、型の判定とそれに伴う場合分けを実行するタイミングにある。前者は、判定と場合分けをGetFlag/SetFlagメソッドが呼ばれるごとに実行するが、後者はコンストラクタで1回実行するだけである。
この差は、GetFlag/SetFlagメソッドの実行回数が多ければ速度差となる。実際に両者のコードを自動テストで比較したが、後者のほうがわずかに速い結果になった(あくまで筆者が開発中のプログラムでの結果なので、別のプログラムでも同じ結果になるとは限らないが)。この程度であればまだそれほど大きな差ではないが、このような判定と場合分けの種類が増えれば、その差は広がっていくだろう。逆にいえば、ソースコードの体質改善を進め、速度を犠牲にせずに判定と場合分けを増やす自由を得るために、このようなコードは有効だといえる。これが、ラムダ式を、整数や文字列と同じように湯水のごとく使うプログラミングがもたらした成果である。
もちろん、このようなコードがつねに優れている、最善である、このようなコードを書くべきである、筆者は推奨する、といいたいわけではない。これは、たまたま筆者が書いたコードの一例にすぎない。最善ではないかもしれないし、つねに良い結果を出すとは限らない。しかし、C# 3.0ではありうる選択肢の1つに入ったのだろう……と思うのである。
INDEX | ||
[完全版]究極のC#プログラミング | ||
ラムダ式を使用した事例/デザインパターン・ミニカタログ | ||
1.[補遺]ラムダ式を使用した事例 | ||
2.C# 3.0 デザインパターン・ミニカタログ | ||
「[完全版]究極のC#プログラミング」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|