DIContainer with AOP顛末 まとめ

.NETのDIContainerにおけるAOPの実装に関してここしばらくすったもんだしていたのですが結果としてはRealProxyを使用したAOP実装が現状の.NETにおいては使いやすさと制約(制限)とのバランスが最も取れているという結論に達しました。

公開されているものも含めて現在の.NETプラットホームで可能なAOP実装の手法は以下の3つに分類されます。
(実際に使用されているものも含めて殆どの.NETのAOP実装を見ましたが全て以下に分類されると考えています)

・ContextBoundObjectとContextAttribute属性を利用する(Annotation 方式)

・ReflectionとMSILのEmitionによる型の動的な生成を利用する(TypeEmition 方式)

・Realproxy派生クラスによるメソッドインターセプションを利用する(Proxy 方式)

  • Annotation 方式

MSDNの記事(http://msdn.microsoft.com/msdnmag/issues/03/03/contextsinnet/)等で紹介されている正統派?の手法ではありますがAspectを適用する対象はContextBoundObjectという比較的ヘビーなクラスの使用を強制されること、メッセージシンク(IMessageSink)の実装クラスが必要なこと、AspectをAnotationとして指定する等、制約が多すぎてちょっと使う気になれませんでした。ContextBoundObjectは処理コストの面からも疑問符がつきます。


  • TypeEmition 方式

実は.NETにおけるAOP実装を調べた中で最も多かったタイプの方式です。対象の型の情報を元に.NETアセンブリ、モジュール、タイプをオンザフライで生成すると共にフィールド、メソッド等実装の全てをMSILの形式で出力するため本来の型の実装とAspectを編みこむことが可能になっています。しかし中間言語であるMSILの生成は特殊なOPCODEをストリームに出力していくので非常に分かり難くなりがち(少なくとも私には解り難い)です。また、MSILをダイレクトに出力すると柔軟性に欠けるためそれを補うためにクラス設計にコンポジション等を採用したり等、実装が大規模になりがちです。


  • Proxy 方式

S2.NETや拙作のContainerが採用している方法です。RealProxyの派生型はその対象となった型の全てのメソッドの実行機会を補足(インターセプト)できる、という振る舞いを利用してAspectを織り込むことを実現しており非常にシンプルな実装が実現できます。ただしRealProxyはメソッドの補足の対象をinterface又はMarshalByRefObjectの派生型に制限しており、これがそのままこの方式の制限となります。



個人的には「TypeEmition 方式」にも食指が動き先日日記上で紹介したDynamicProxy(http://www.castleproject.org/index.php/DynamicProxy)やDynaProx.NET(http://workspaces.gotdotnet.com/dynaprox)も参考にして適用の可能性を探ってみたのですがどうもMSILをゴリゴリとストリームに出力する、という方法がしっくりこないので採用を見送りました。生成したMSILが正しく出力されているかはアセンブリを実ストレージに出力してILDASM等で内部を見ないと確認できない、という危うさ、必要な機能を全て用意しようとすると実装が大規模になりアセンブリを分割する可能性が高いという理由もあります。(私の場合リッチクライアント用のフレームワークなのでLog4Netは仕方が無いにしろその他として配布するアセンブリをフレームワークのアセンブリ以外に増やしたくないんです)



ある機能をフレームワークに組み込む場合に実現する方法が複数あってそれぞれに長所と短所がありその中でどれに決定するかを悩むというケースは今までにも幾度となく経験してきていますが間違い無いのは「できるだけシンプルな方法」を選択することだと思っています。今回のAOPの実装に関しても多少の制約はあってもProxy方式でのAOP実装のシンプルさはそれを補って余りあるといえます。

追記:今さらだけど.NETはなぜ派生型のメソッドのデフォルトを"virtual"にしなかったのだろう。