動的処理

16 12月

今日のテーマは動的(dynamic)です。

最初に注意しておきたいのは、動的と言っても何を動的に行うか、いろんなやり方があって、いろんな用途があります。動的ローディング、動的コード生成、動的型付けなどなど、「動的~」の「~」の部分をちゃんと考えましょう。

動的ローディング

既知の型を、未知のDLLから読み込みます。

例えば以下のような感じ。System.Reflection.AssemblyクラスのLoadメソッドなどを使ってDLLを読み込んで、System.Activator.Createメソッドでインスタンスを作ります。

インスタンスを作る部分だけ面倒ですが、一度インスタンスを作ってしまえば、あとは普通に(特に苦労することなく)使えます。

用途1: プラグイン

プラグイン、つまり、アプリ本体とは別の第3者がアプリへの拡張機能を提供する場合、まさに、既知の型を未知のDLLから読み込むことになります。

用途2: ホット デプロイ

アプリ本体を稼働させたままで、部分的に機能を置き換えたい場合があります。その場合、置き換えたい部分をプラグインとして作っておいて、更新が必要なときに再読み込み(あるいは、DLLファイルの監視をしておいて、新しくなったら再読み込み)する仕組みを作ります。

便利ライブラリ: MEF

こういうプラグイン的な動的ローディングをサポートするためのフレームワークとして、MEF(Managed Extensibility Framework)というものがあります。

MEFは、.NET Framework 4からは標準ライブラリに取り込まれました。System.ComponentModel.Composition名前空間以下のクラスがMEFの実体です。

リフレクション(型情報使った処理)

型がどういうメンバー(メソッドやプロパティなど)を持つかというような、型情報を使った処理を書きたいことがあります。

この手の情報は、実行時には必ずしも必要のない情報で、プログラミング言語によっては実行時に残さない(コンパイル時にだけ使う)こともあります。そういう意味で、メタデータ(metadata: データに関するデータという意味。この場合、プログラムというデータを作るためのデータ)と呼ばれたりもします。

.NETでは、GetTypeメソッドなどを使って型情報を実行時に取得できます。型情報は、Type型のインスタンスとして表されます。

型情報を実行時に取得することをリフレクション(reflection: 反射。ここでは自己反映的という意味合い)と呼びます。

用途: シリアライズ

型情報を使った処理の代表格は、データのシリアライズ(serialize: 直列化する)です。独自のバイナリ形式であったり文字列化であったり、一定のルールでデータを保存・復元することをシリアライズ(復元の方はデシリアライズ)と呼びます。

コンピューターをまたいだ通信でデータを受け渡ししたり、ファイルなどに保存するために使います。

もちろん、型ごとに独自のシリアライズ処理を書いても構わないんですが、だいたいは定型的な処理になるので、個別に書くのは面倒です。そこで、型情報を使って動的にシリアライズしたりします。

具体例: XML化、JSON化

データ形式が何でもいいなら、自分で処理を書く必要はなく、標準ライブラリ機能を使えばいいでしょう。以下のようなクラスが標準提供されています。

その他、オープン ソースなライブラリで、Json.NETDynamicJsonなども有名です。

動的コード生成

静的な処理(コンパイル済みのコードの実行)と比べると、リフレクションはそれなりに重たい処理です。もともとコンパイル時に全部済ましてしまうような処理をその都度掛ける分重たいわけです。だいたい、2・3桁くらい遅いです。2倍じゃなく、2桁差。

そこで、動的にコードを生成して、その生成したコンパイル済みのコードを取っておいて、使いまわそうという発想が出てきます。この方法なら、重たい処理は最初の1回だけでよくて、2回目以降は十分高速になります。

.NETでは、動的コード生成の手段として以下のようなものがあります。

  • 式ツリー
    • System.Linq.Expressions名前空間以下のクラスを使います
    • 名前通り、ツリー構造のデータを使ってプログラムを表現します
    • ラムダ式からの生成もできます。
    • .NET 3.0で式相当のコード、.NET 4で一通りなんでもコード生成できるようになりました
  • System.Reflection.Emit.DynamicMethodクラス
    • IL(.NETの中間言語、アセンブリ言語)を直接記述します
    • 式ツリーが入る以前の、旧時代の遺物ではあります
  • CodeDOM
    • System.CodeDom名前空間以下のクラスを使います
    • 文字列でC#などのソース コードを作って、それをコンパイルします
    • 内部的に、C#などのコンパイラーを直接呼び出しているだけです
      • 現状、あまり使い勝手の良い仕様にはなっていません
      • 正直、Roslyn待ち

用途1: リフレクションの高速化

本節冒頭の通り、一番の用途はリフレクションの高速化です。

たいていのシリアライズ用のライブラリでは、内部的には動的コード生成しています。

用途2: スクリプティング

アプリ中で、ちょっとしたスクリプト言語を使いたい場合があります。アプリ中でのスクリプト実行は、当然、動的コード生成になります。

昨日説明した通り、IronPythonなどが利用できます。

ゆるい型付け

通常、C#のような、静的で厳密な型を持つ言語では、あるオブジェクトに途中でプロパティやメソッドを足すということはできません。

しかし、C# 4.0でdynamicキーワードが導入されて、ゆるい型(実行時にいろいろ追加可能)が使えるようになりました。

dynamic d = new ExpandoObject();
d.X = 10; // 
この瞬間、 X というメンバーが追加される
d.Y = 20; // 
同上、追加
Console.WriteLine(d.X + d.Y);

ここで使ったExpandoObjectを含め、System.Dynamic名前空間以下のクラスを使います。

用途1: DLL間の依存関係をなくす

通常、クラスなどの型を使いたければ、その型を定義しているDLLをコンパイル時に参照する必要があります。そのDLLに依存しないとコンパイルできません。

しかし、まれにですが、無理やりにでもDLLの依存関係を切りたいことがあります。結構無理やりな手段ですが、そういう場合にdynamicキーワードが使えなくもないです。

用途2: スクリプト言語連携

IronPythonなどの基盤であるDLR(dynamic language runtime)は、内部的にSystem.Dynamic名前空間のクラスを使います。dynamicキーワードを使って、簡単に連携可能です。

というよりも、.NET 4の動的機能、System.Dynamic名前空間内のクラスは、DLRの成果を.NETに取り込んだものです。

用途3: ゆるい型のデータ連携

JSONを使ったRESTサービスなどでは、いわゆるスキーマレス(schema-less: データ構造をきっちり決めてない)になっていることがあります。

dynamicキーワードを使えば、この手のスキーマレスなデータとの連携がお手軽になります。

動的に型の挙動を変える

ある型の特定のメソッドの挙動を動的に差し替えたり、インターフェイスを継承した型を動的に生成したりしたいことがあります。

この場合、動的に型を作ることになります。型の動的生成には、以下のようなクラスが使えます。

  • System.Reflection.Emit.TypeBuilder
    • 型情報(Type型)を動的に作ります
    • メソッドなどの挙動も動的にコード生成できます
    • 作った型情報を元に、Activator.Createします
  • System.Runtime.Remoting.Proxies.RealProxy
    • インターフェイスのメソッド呼び出しをフックして、別の処理をはさむ機能を提供します

用途1: 透過プロキシ

例えば、以下のようなクラスを考えてみます。

public class IService
{
    void SendMessage(string message);
}

public class Service : IService
{
    public void SendMessage(string message)
    {
        // 
具体的な処理
    }
}

普通にインスタンスを作って普通に使うと、以下のようになります。

これに対して、インターフェイスとクラスの間に別の処理をはさみたいことがあります。わかりやすい例は、通信をはさんで、サーバー上で処理を行いたい場合です。

こういう、間に挟むもののことをプロキシ(proxy)と呼びます。特に、この例のように、利用側からするとプロキシが挟まっていることを意識しないで使えるもののことを透過プロキシ(transparent proxy)と呼びます。

プロキシ用途の場合、ビルド時コード生成という手段もあるんですが、動的に(実行時に)型生成することも多いです。

用途2: モック

あるメソッドをテストしたいときに、そのメソッドが別のメソッドに依存している場合、どうすればいいでしょう。

依存先のメソッドが完成するまで待つと、どんどん開発が遅れます。また、最初は正しく動いていたけど、あとから依存先の方にバグが混入したら、こちらのメソッドまでテストが通らなくなったり。

ということで、依存先のメソッドをモック(mock: 模造品)で差し替えたいことがあります。こういう場合、型を継承した別クラスを動的に作って、所望のメソッドだけ置き換えるというような方法を取ります。

便利ライブラリ: Moq

モック生成のためだけにTypeBuilderなどを使うのは結構大変なので、補助してくれるライブラリを使うのが一般的です。モック生成用のライブラリは、結構いろんなものがオープン ソースで提供されています。

有名どころだと、Moq(もきゅ?)とかでしょうか。以下のような使い方ができます。

var mock = new Moq.Mock<IComparer<int>>();
mock.Setup(m => m.Compare(0, 1)).Returns(-1);
mock.Setup(m => m.Compare(1, 0)).Returns(1);
mock.Setup(m => m.Compare(0, 0)).Returns(0);

var c = mock.Object;
Console.WriteLine(c.Compare(1, 0));

Moqは、NuGetでもインストールできます。

型情報の動的生成

メソッドなどの挙動までは必要なく、「この型はこういうプロパティを持っている」というような、メタデータだけ動的に作って提供したいことがあります。以下のようなクラスを使います。

  • System.ComponentModel.ICustomTypeDescriptor
    • .NET 4/Silverlight 4以前で使います
    • Type型とは別系統で、TypeDescriptorなどのクラスを返す仕組みでした。Type型を返すことでも同じことができるので、後述のICustomTypeProviderで置き換わる予定です
  • System.Reflection.ICustomTypeProvider
    • .NET 4.5/Silverlight 5以降で使います
    • Type型で型情報を返します

用途: データ バインディング

WPFやSilverlightでは、UIの方(いわゆるビュー。XAMLで書くやつ)には、「ここにXの値を表示したい」というような印だけ入れておいて、実際のデータは外部から渡します。

この際、渡したデータの型情報に基づいて、文字列から数値への変換や、変換に失敗した際のエラー処理などを自動的にやってくれています。こういう仕組みをデータ バインディング(data binding)と呼びます。

そこで問題になるのは、渡すデータがゆるい型の場合。データ バインディングは内部的にいろいろ動的な処理をしているわけですが、渡す側も動的だと、型情報が取れなくて困ります。

そこで、ゆるい型に対して、別途、ちゃんとした型情報をでっちあげるために使うのが、ICustomTypeProviderなどのインターフェイスです。このインターフェイスを実装している場合、実際の型情報(GetTypeした結果)よりも優先して、別途用意した型情報に基づいたデータ バインディングを行ってくれます。

補足

今日、延々と説明してきた動的処理の類は、.NETの中でも一番不安定というか、変化が激しい部分ですねぇ。進化の真っただ中にあるのが原因ではありますが、ちょっと目を離したすきになんか変わっちゃってます。

DynamicMethodが過去の遺物(式ツリーを使いましょう)になってたり、ICustomTypeDescriptorICustomTypeProviderに変わったり。

Windows 8, Metroアプリ向け.NET

実は、Windows 8では、.NET Frameworkが2系統に分かれます。

  • デスクトップ向け
    • 今まで通りです
  • Metro(スレートPC向けのタッチUI)向け
    • デスクトップ向けとは別系統ライブラリが入った.NET Frameworkになります

この、Metro向け.NETでは、Type型をはじめとした、リフレクション回りの仕様が大きく変わります。

  • 古いスタイルのAPIを削除
    • DynamicMethodなどは使えなくなっています
  • 現状のType型を、Type型とTypeInfo型の2つに分離
    • System名前空間とSystem.Reflection名前空間の間の依存を切るためだそうです
    • 元々、.NETのメタデータ上は2つに分かれています
      • TypeRef: 型の識別にだけ使う情報 → Type
      • TypeDef: リフレクション用の詳細な情報 → TypeInfo
  • System.Reflection.Emit廃止
  • System.Runtime.Remoting廃止

 

基本的には「重複削除」なので問題ないんですが、今日紹介した中でいうと、型の挙動を変える類の処理(透過プロキシやモック)ができなくなっています。

もちろん、今はまだプレビュー版なので、製品版までに変更があると思います。

1件のフィードバック to “動的処理”

Trackbacks/Pingbacks

  1. 単体テスト « C#たんっ! - 2011/12/22

    […] 動的処理の回で書きましたが、テストのお供にモック(mock: 模造品)生成フレームワークがあります。 […]

コメントを残す