リフレクションとは正確には何ですか?このテーマに関するウィキペディアの記事を読み、プログラムが実行時に自分自身を変更できる一種のメタプログラミングであることを理解しましたが、これはどういう意味ですか? これはどのような状況で良いアプローチで、いつ使用するのが最適ですか?
11 に答える
リフレクションは、実行時にオブジェクトの属性についてオブジェクトを照会できる機能です。たとえば、Python、Java、および .Net には、オブジェクトのインスタンス変数またはメソッドを見つけることができる機能があります。
リフレクションのアプリケーションの例は、O/R マッピング レイヤーです。リフレクションを使用して、実行時にプロパティをクエリし、インスタンスに動的に入力することでオブジェクトを構築するものもあります。これにより、アプリケーションを再コンパイルすることなく、ある種のデータ ディクショナリのメタデータに基づいてプログラムでこれを行うことができます。
簡単な例を挙げると、Python を使用します。これは、リフレクション機能が非常に使いやすく、Java や .Net より定型文が少ないためです。
ActivePython 2.5.2.2 (ActiveState Software Inc.) based on
Python 2.5.2 (r252:60911, Mar 27 2008, 17:57:18) [MSC v.1310 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> class foo:
... def __init__(self):
... self.x = 1
...
>>> xx = foo() # Creates an object and runs the constructor
>>> xx.__dict__ # System metadata about the object
{'x': 1}
>>> a = xx.__dict__ # Now we manipulate the object through
>>> a['y'] = 2 # its metadata ...
>>> print xx.y # ... and suddenly it has a new instance variable
2
>>>
ここで、基本的なリフレクションを使用して、任意のオブジェクトのインスタンス変数を調べました。Pythonの特殊変数__dict__
は、変数 (またはメソッド) 名をキーとするメンバーのハッシュ テーブルを持つオブジェクトのシステム プロパティです。オブジェクトをリフレクティブに調べ、リフレクション機能を使用して 2 番目のインスタンス変数を人為的に挿入し、インスタンス変数として呼び出すことで表示できます。
インスタンス変数が固定されているため、この特定のトリックは Java または .Net では機能しないことに注意してください。これらの言語の型システムでは、Python の「ダック」型システムのように、実行時に新しいインスタンス変数を追加することはできません。ただし、型定義で宣言されたインスタンス変数の値を反射的に更新することはできました。
また、リフレクションを使用してメソッド呼び出しを動的に構築し、パラメーターに基づいてオブジェクトをインスタンス化するなど、さまざまな巧妙なトリックを実行することもできます。たとえば、特定の機能がオプションであるある種のプラグイン ベースのシステムがある場合、明示的なメタデータを必要とせずに、リフレクションを使用してプラグインが提供するサービスについて (おそらく特定のインターフェイスが実装されているかどうかを照会することによって) 照会できます。
OLE オートメーションなどの多くの動的言語インターフェイスは、インターフェイスの不可欠な部分としてリフレクションを使用します。
実行時にコードを変更するのではなく、オブジェクトを調べて、型を静的に知らなくてもコードを実行するように要求します。
それを説明する簡単な方法の 1 つは、「静的に型付けされた言語を動的に動作させるためのやや面倒な方法」です。
編集:用途:
- 構成 (たとえば、型とプロパティを指定する XML ファイルを取得し、適切なオブジェクトを構築します)
- テスト (名前または属性で識別される単体テスト)
- Web サービス (少なくとも .NET では、コア Web サービス エンジンで多くのリフレクションが使用されます)
- 自動イベント ワイヤリング - メソッドに適切な名前を付けます。たとえば
SubmitButton_Click
、ASP.NET はそのメソッドをSubmitButton
のClick
イベントのハンドラとしてアタッチします (オートワイヤリングがオンになっている場合)。
それは良い考えですか?まあ、代替手段が苦痛な場合にのみ。邪魔にならない場合は、静的型付けを好みます。そうすれば、コンパイル時に多くの利点が得られ、高速になります。しかし、必要な場合は、リフレクションを使用すると、他の方法では不可能なさまざまなことができます。
リフレクションは、考えられる少なくとも 1 つのプロジェクトで役に立ちました。私たちは、一定の間隔で多くの重要なビジネス プロセスを実行する内部「プロセス マネージャー」プログラムを作成しました。プロジェクトは、コアが実際には 30 秒ごとに起動し、実行する作業をチェックするタイマー オブジェクトを備えた単なる Windows サービスになるように設定されています。
実際の作業は、クラス ライブラリ (適切に "WorkerLib" と呼ばれます) で行われています。特定のタスク (ファイルの移動、リモート サイトへのデータのアップロードなど) を実行するパブリック メソッドを持つクラスを定義します。コア サービスは、呼び出しているメソッドについて何も知らなくても、ワーカー ライブラリからメソッドを呼び出すことができるという考えです。これにより、データベースでジョブのスケジュールを作成したり、コア システムを変更することなくクラス ライブラリに新しいメソッドを追加したりすることができます。
基本的な考え方は、コア サービスでリフレクションを使用して、スケジュールを定義するデータベースに名前を保存したメソッドを実行できるということです。実際にはかなりきれいです。私たちのコア サービスは堅実で、必要に応じてジョブの実行を処理しますが、実際のワーカー ライブラリは、コアが気にすることなく、必要に応じて拡張および変更できます。
さらに説明が必要な場合は、お気軽にお尋ねください。これは、リフレクションによって物事が本当に簡単になるという現実世界のシナリオを説明するために、私が考えることができる最良の方法でした.
私が頭のてっぺんから思いつく最初の良い例は、コンパイル時にどのメソッドが存在するかをコンパイル時に知らずに、特定のオブジェクトで一連のメソッドを実行する必要がある場合です。
単体テスト フレームワークを例に取ります。すべての単体テストの実行を担当するテスト ランナー クラスは、メソッドの名前を事前に知りません。わかっているのは、「test」という接頭辞が付けられることだけです (または、Java 5 の場合は、注釈が付けられ@Test
ます)。したがって、テストクラスが与えられると、そのクラスに反映され、その中のすべてのメソッドのリストが取得されます。次に、これらのメソッド名を文字列として繰り返し処理し、"test" で始まる場合はオブジェクトのメソッドを呼び出します。反省しないと無理です。これはほんの一例です。
実際、リフレクションはコードのアンプのようなものと考えるべきです。良いコードはより良く、よりきれいになり、悪いコードはより悪くなる可能性があります。それは何をするためのものか?これにより、コードを作成する時点で何をするか完全にはわからないコードを書くことができます。大まかなアイデアはありますが、プログラムのコンパイル時に実行されるオブジェクト、メソッド、およびプロパティをコーディングしないで済みます。
プログラムが構成値に基づいてコードを実行できるようにするという他の投稿は正しいですが、その真の力は、オブジェクト指向プログラミングのルールを大幅に曲げることができることです。それは本当にそれがすることです。安全対策をオフにするようなものです。プライベート メソッドとプロパティには、リフレクションを介して、他のほとんどすべてのものと同様にアクセスできます。
MS がリフレクションを使用する優れた例は、データ オブジェクトのデータ バインディングです。ドロップダウン リストなどにバインドするオブジェクトのテキスト フィールドとデータ フィールドの名前を指定すると、コードがオブジェクトを反映して適切な情報を引き出します。データ バインディング オブジェクトは同じプロセスを何度も繰り返しますが、バインドする必要があるオブジェクトの種類を認識していません。リフレクションは、考えられるすべてのケースを処理するための小さなコードを作成する便利な方法です。
別の例: データベースの出力 (名前付きの列を持つ行のセット) を取得し、それをオブジェクトの配列にフィードするコードを使用しています。行を反復処理し、ターゲット オブジェクトに同じ名前と型のプロパティがある場合は、それを設定します。これにより、データ取得コードは次のようになります。
SqlDataReader sdr = Helper.GetReader("GetClientByID", ClientID);
Client c = new Client();
FillObject(sdr, c);
return c;
リフレクションは、(基本的に)コンパイラーが使用できる型情報を照会するプログラムの機能です。したがって、たとえば、型の名前を指定すると、それに含まれるメソッドを照会できます。次に、メソッドごとに、取得するパラメーターのタイプなどを照会できます。
これは、アプリケーションの動作を指定する構成ファイルがあるランタイム構成に役立ちます。構成には、使用する必要のある具象タイプの名前が含まれている場合があります(IOCコンテナーの場合によくあることです)。リフレクションを使用すると、この具象タイプのインスタンスを(リフレクションAPIを介して)作成して使用できます。
Java では基本的に、事前に知らなくてもクラスをインスタンス化する方法です。プログラムで使用するクラスを追加することで、ユーザーが構成ファイルを変更できるようにしたいとします (あるインターフェイスの多数の実装があるとします)。リフレクションを使用すると、名前、メソッド シグネチャなどに基づいてオブジェクトを作成できます。. そしてそれをあなたのインターフェースにキャストします。
リフレクションはランタイム構成に役立ち、システムの一部を外部構成によって駆動できるようにします。
たとえば、クラス ファクトリは、入力ファイルに基づいてさまざまな具象型を構築できます。この場合、具象型は、ビルダー インターフェイスを使用するのではなく具象コンストラクターを呼び出すために、さまざまな構成情報を必要とします。(リフレクションを使用して配置されるオブジェクトのコンストラクター メソッド)。
例を挙げましょう。
プログラミングの演習として、mp3 ファイル チェッカーを作成しました。私の音楽ライブラリをスキャンし、興味のある id1/id2 タグを DataGridView に表示します。UI コードがそのクラスについて何も知らなくても、リフレクションを使用して mp3 情報クラスからプロパティを取得します。表示される情報を変更したい場合は、mp3 情報クラスを編集するか、(クラスの作成方法に応じて) その構成を変更することができ、UI を更新する必要はありません。
また、依存性注入を使用して、データ ライブラリ クラスを交換するだけで、デジタル写真に関する情報を表示するために同じものを最初から使用できるようになったことも意味します。
アセンブリにはモジュールが含まれ、モジュールには型が含まれ、型にはメンバーが含まれます。リフレクションは、アセンブリ、モジュール、および型をカプセル化するオブジェクトを提供します。リフレクションを使用して、型のインスタンスを動的に作成したり、型を既存のオブジェクトにバインドしたり、既存のオブジェクトから型を取得したりできます。その後、型のメソッドを呼び出したり、そのフィールドやプロパティにアクセスしたりできます。リフレクションの一般的な用途には、次のようなものがあります。
- Assembly を使用して、アセンブリを定義して読み込み、アセンブリ マニフェストにリストされているモジュールを読み込み、このアセンブリから型を見つけてそのインスタンスを作成します。
- Module を使用して、モジュールを含むアセンブリやモジュール内のクラスなどの情報を検出します。モジュールで定義されているすべてのグローバル メソッドまたはその他の特定の非グローバル メソッドを取得することもできます。
- ConstructorInfo を使用して、コンストラクターの名前、パラメーター、アクセス修飾子 (public または private など)、および実装の詳細 (abstract または virtual など) などの情報を検出します。Type の GetConstructors または GetConstructor メソッドを使用して、特定のコンストラクターを呼び出します。
- MethodInfo を使用して、メソッドの名前、戻り値の型、パラメーター、アクセス修飾子 (public または private など)、および実装の詳細 (abstract または virtual など) などの情報を検出します。特定のメソッドを呼び出すには、Type の GetMethods または GetMethod メソッドを使用します。
- FieldInfo を使用して、フィールドの名前、アクセス修飾子 (public または private など)、実装の詳細 (static など) などの情報を検出し、フィールド値を取得または設定します。
- EventInfo を使用して、イベントの名前、イベント ハンドラーのデータ型、カスタム属性、宣言型、反映型などの情報を検出し、イベント ハンドラーを追加または削除します。
- PropertyInfo を使用して、プロパティの名前、データ型、宣言型、反映型、読み取り専用または書き込み可能ステータスなどの情報を検出し、プロパティ値を取得または設定します。
- ParameterInfo を使用して、パラメーターの名前、データ型、パラメーターが入力パラメーターか出力パラメーターか、メソッド シグネチャ内のパラメーターの位置などの情報を検出します。