透過プロキシでアスペクト指向プログラミング (2)

前回の記事で、NorthwindBusiness クラスの透過プロキシを生成するときに、

var nw = CrossCuttingProxy.CreateProxy<NorthwindBusiness>();

というコードを記述していましたが、
MarshalByRefObject の代わりに ContextBoundObject クラスを継承させると、通常のコンストラクターを利用することができます。
ただし、クラスに属性を付ける必要があります。
次のように実装します。

以上で、

var nw = new NorthwindBusiness();

と記述できるようになりました。
なお、上記のコードには現れていませんが、
コンストラクターが呼び出されたときに、CrossCuttingProxy クラスの Invoke メソッドが呼び出されます。

 

前回:透過プロキシでアスペクト指向プログラミング (1)
次回:メソッドチェーンでアスペクト指向プログラミング

作成したサンプル
CrossCuttingConsole (GitHub)

バージョン情報
C# 7.0
.NET Framework 4.5

参照
RealProxy クラス
アスペクト指向プログラミング (Wikipedia)

透過プロキシでアスペクト指向プログラミング (1)

前回のインターフェイスに対する透過プロキシでは、RealProxy クラスを利用してインターフェイスに対する透過プロキシを生成し、
その実装クラスが存在していなくてもメソッドに処理を割り当てることができました。

改めて、透過プロキシ (transparent proxy) の主な特徴を整理すると次のようになります。

  • 対象となる型は、MarshalByRefObject を継承したクラス、またはインターフェイス
  • 各メンバーが呼び出されたときの挙動を上書きできる

今回は MarshalByRefObject を継承したクラスの既存の処理を透過プロキシで拡張して、
アスペクト指向プログラミング (AOP) を実践してみます。

一般的にアプリケーション設計においては、
ログ出力やデータベース トランザクションなど、いろいろなビジネス ロジックに共通する横断的関心事があります。
例えばデータベース トランザクションであれば、以前にファントム リードとその解決方法などで書いている通り、
ビジネス ロジックごとに TransactionScope に関する同じようなコードを繰り返し記述することになります。
この部分をアスペクト (側面) として分離し、属性として記述できるようにすることで再利用性を高めることを目指します。

まず次のコードで示す通り、RealProxy を継承した CrossCuttingProxy クラスと、アスペクトを表す属性を定義します。

ログ出力を表す TraceLogAttribute クラスとデータベース トランザクションを表す TransactionScopeAttribute クラスを用意し、
既存の処理の前後に割り込むようにしてそれぞれの処理を追加しています。

以上のようにすれば、ビジネス ロジックを次のように記述するだけで済むようになります。

実行結果です。ログが出力されています:

CrossCuttingConsole

 

前回:インターフェイスに対する透過プロキシ
次回:透過プロキシでアスペクト指向プログラミング (2)

作成したサンプル
CrossCuttingConsole (GitHub)

バージョン情報
C# 7.0
.NET Framework 4.5

参照
RealProxy クラス
アスペクト指向プログラミング (Wikipedia)

インターフェイスに対する透過プロキシ

前回の DLR で名前付き引数を使うでは、メソッドに引数として「キーと値のペア」を渡す方法を考え、
動的言語ランタイム (DLR) を利用する方法を紹介しました。
しかし、同じ処理を複数の場所で呼び出す必要があるなど、再利用性を重視する場合は、
なるべく静的にシグネチャを解決する方法がよいでしょう。

そこで今回は、呼び出すサービスをインターフェイスとして定義し、透過プロキシ (transparent proxy) を利用してみます。
先にコードを示します。

このコードでは、RealProxy クラスを継承した HttpProxy<IService> クラスを作成しています。
RealProxy はプロキシの実体となるものであり、
その外層である透過プロキシを RealProxy.GetTransparentProxy メソッドで取得できます。

一方、利用する側の Program.cs では、ICgisService インターフェイスを定義しておきます。
その透過プロキシを生成すれば、ICgisService インターフェイスを実装するクラスが存在しなくてもメソッドを呼び出すことができ、
実体は HttpProxy<IService> クラスの Invoke メソッドとなります。

また、BaseUriAttribute クラスを定義しており、
呼び出される Web API のベースとなる URI を属性で指定できるようにしています。

WCF における契約プログラミングでは、クライアント側とサーバー側で同一のインターフェイスを利用し、
クライアント側からのアクセスを上記のように透過プロキシで実装する方法があります。
方法 : ChannelFactory を使用する にある通り、ChannelFactory.CreateChannel メソッドで透過プロキシを生成します。

前回:DLR で名前付き引数を使う
次回:透過プロキシでアスペクト指向プログラミング (1)

作成したサンプル
TransparentHttpConsole (GitHub)

バージョン情報
C# 7.0
.NET Framework 4.5

参照
RealProxy クラス
方法 : ChannelFactory を使用する

DLR で名前付き引数を使う

C# で、メソッドに引数として「キーと値のペア」を渡す方法を考えてみます。
例えば HTTP で GET でアクセスするときに URL でクエリ文字列を指定する場合が挙げられます。

よく使われているのは、メソッドの引数に Dictionary や匿名型オブジェクトを渡す方法かと思います。
以下の HttpHelper クラスのように実装します。
なお、WebClient クラスではクエリ文字列 (QueryString プロパティ) は NameValueCollection 型であるため、
受け取った情報を NameValueCollection 型に変換しています。

なお、ここでは題材として CGI’s 郵便番号検索 API を利用しています。

さて、動的言語ランタイム (DLR)名前付き引数を利用して、引数の情報を実行時に解決できないかと考えると、
次のような方法を思いつきます。

dynamic http = new DynamicHttpProxy();
var result = http.Get(Uri_Cgis_Xml, zn: "402", ver: 1);

実際、DynamicObject クラスを継承した DynamicHttpProxy クラスを次のように作れば可能です。

TryInvokeMember メソッドの中で、引数の名前は binder.CallInfo.ArgumentNames で取得できます。
ただし、引数の名前を指定せずに渡された分はここに含まれない (コレクションの長さが変わる) ため注意が必要です。

 

また、C# 7.0 で追加された ValueTuple を利用して、

var result = HttpHelper.Get(Uri_Cgis_Xml, (zn: "6050073"));

とする案もありましたが、

  • 要素が 1 つ以下の場合、タプル リテラルを記述できない
  • コンパイル後はフィールド名が残らないため、実行時に動的に取得できない

という制約により実現できませんでした。

次回:インターフェイスに対する透過プロキシ

作成したサンプル
DynamicHttpConsole (GitHub)

バージョン情報
C# 7.0
.NET Framework 4.5

参照
動的言語ランタイムの概要
名前付き引数と省略可能な引数
タプル – C# によるプログラミング入門

エンティティを匿名型で手軽に実装する (2)

前回のエンティティを匿名型で手軽に実装する (1) で作成した EntityType<TEntity> では、
インスタンスを初期化するために毎回 ConstructorInfo を経由していましたが、
少し重い処理のため、回数が多いと処理時間に影響します。

そこで、あらかじめコンストラクターをラムダ式の式ツリーからコンパイルしておくという方法があります。
先にコードを示します。

取得した ConstructorInfo をもとに、

p => new TEntity((int)p[0], (string)p[1])

のような処理に相当する式ツリーを構築してコンパイルすることで、object[] から TEntity を初期化する関数が得られます。

CreateEntity メソッドの呼び出しを 100 万回実行して計測してみると、手元の環境だと前回のコードでは

  • EntityType の初期化: 0.0006 秒
  • CreateEntity メソッド 100 万回: 0.6 秒

だったのが、今回のコードでは

  • EntityType の初期化: 0.006 秒
  • CreateEntity メソッド 100 万回: 0.06 秒

になりました。

 

前回: エンティティを匿名型で手軽に実装する (1)

作成したサンプル
ExpressionsConsole (GitHub)

参照
Expression<TDelegate> クラス

エンティティを匿名型で手軽に実装する (1)

ここでは、次のようなものをエンティティ型と呼ぶことにします。

  • プロパティが定義されている
  • 各プロパティの値を指定してインスタンスを初期化する方法 (コンストラクターなど) が用意されている

通常、これを C# で実装すると次の図のようになり、少し手間がかかると思うことがあります。
Class1 はコンストラクターの実装が必要であり、Class2 はインスタンス生成時にプロパティ名を指定する必要があります。

エンティティ型の宣言

 

そこで今回は、匿名型を活用してこのようなエンティティ型を定義する方法を考えてみたいと思います。

実は匿名型は、プロパティは読取り専用で定義され、プロパティの数だけ引数を持つコンストラクターが 1 つだけ定義されます。
つまり、ちょうど上記の Class1 のような構造をしています。

このコンストラクターにアクセスできれば、任意のタイミングで匿名型のインスタンスを生成できるというわけです。
そこで、次のように EntityType クラスを実装してみました。

匿名型によりプロパティを宣言し、CreateEntity メソッドによりインスタンスを初期化できます。
CreateEntity メソッドの引数は params object[] のため IntelliSense は利きませんが、簡単に書けている気がします。
ただし匿名型のため、ローカル スコープを抜けられないという制限があります。

 

次に、これを CSV ファイルの読み込みに応用します。
CsvFile クラスで、読み込んだデータを指定された型に変換します。

(なお、ConvertHelper.cs は省略されています。完全なソースコードは ExpressionsConsole – GitHub にあります。)

このようにすると、CsvFile クラスを呼び出す側では IntelliSense が利き、LINQ to Objects でアクセスできます。

CsvFile (IntelliSense)

 

さて、インスタンスを生成するときに毎回 ConstructorInfo にアクセスするのは少しコストがかかるので、
次回は式ツリーを利用してパフォーマンスを改善したいと思います。

次回: エンティティを匿名型で手軽に実装する (2)

作成したサンプル
ExpressionsConsole (GitHub)

参照
匿名型 (C# プログラミング ガイド)