リフレクションとは
情報工学におけるリフレクション(reflection)は、プログラムが自身の構造を動的に調査し、変更する能力を指します。日本語では「
自己言及」とも呼ばれます。これは、実行中のプログラムが、自身のクラス、メソッド、フィールドなどの情報を取得したり、操作したりできることを意味します。
概要
一般的にリフレクションは、動的な(実行時)リフレクションを指しますが、静的な(コンパイル時)リフレクションをサポートする言語も存在します。リフレクションは、Smalltalk、
Java、
.NET Frameworkなどの仮想マシンや
インタプリタ上で実行される言語でよくサポートされています。一方で、
C言語のような
機械語に直接コンパイルされる言語では、あまりサポートされていません。
リフレクションの主な目的は、プログラムの構造や意味をオブジェクト自体が把握できるようにすることです。この
プログラミングパラダイムは「リフレクティブプログラミング」と呼ばれます。通常、
ソースコードがコンパイルされると、プログラムの構造に関する情報は失われますが、リフレクションをサポートする言語では、
メタデータとしてこれらの情報が保存されます。LISPやForthのようにコンパイル時と実行時の区別がない言語では、コードの解釈とリフレクションの間に明確な境界線はありません。
具体例
多くの
プログラミング言語でリフレクションがサポートされています。以下にいくつかの例を示します。
Javaでは、`java.lang.reflect`パッケージを使用してリフレクションを実現します。
java
// 通常のメソッド呼び出し
Foo foo = new Foo();
foo.hello();
// リフレクションを使ったメソッド呼び出し
Class> clazz = Class.forName("Foo");
Object obj = clazz.newInstance();
Method method = clazz.getMethod("hello");
method.invoke(obj);
前者の例では、クラス名やメソッド名が
ハードコーディングされており、実行時に変更することはできません。しかし、リフレクションを用いた後者の例では、これらの情報を実行時に動的に変更できます。ただし、リフレクションを使用するとコードの可読性が低下し、コンパイル時のエラーチェックの恩恵を受けられないというデメリットもあります。
perl
my $obj = Foo->new();
$obj->hello();
my $class_name = "Foo";
my $method_name = "hello";
my $obj = $class_name->new();
$obj->$method_name();
objectivec
Foo foo = [[Foo alloc] init];
[foo hello];
Class clazz = NSClassFromString(@"Foo");
id obj = [[clazz alloc] init];
SEL selector = NSSelectorFromString(@"hello");
[obj performSelector:selector];
actionscript
var foo:Foo = new Foo();
foo.hello();
var className:String = "Foo";
var obj:Object = new (getDefinitionByName(className))();
obj.hello();
javascript
var foo = new Foo();
foo.hello();
var className = "Foo";
var obj = new window[className]();
obj.hello();
ruby
foo = Foo.new
foo.hello
class_name = "Foo"
obj = Object.const_get(class_name).new
obj.send(:hello)
python
foo = Foo()
foo.hello()
class_name = "Foo"
obj = globals()[class_name]()
getattr(obj, "hello")()
PHP
php
$foo = new Foo();
$foo->hello();
$class_name = "Foo";
$obj = new $class_name();
$obj->hello();
C#
C#では、リフレクションを利用して、アセンブリからクラスを読み込み、メソッドの情報を取得できます。以下の例では、コマンドライン引数でアセンブリ名を指定し、そのアセンブリ内のメソッドを調査し、カスタム属性によって変更されたメソッドを特定します。
csharp
// アセンブリの読み込み
Assembly assembly = Assembly.LoadFrom(args[0]);
// アセンブリ内のメソッドを検索
foreach (Type type in assembly.GetTypes())
{
foreach (MethodInfo method in type.GetMethods())
{
// カスタム属性の取得
var modifiedAttribute = method.GetCustomAttribute
();
if (modifiedAttribute != null && method.GetParameters().Length == 0)
{
Console.WriteLine($"Method: {method.Name}, Return Type: {method.ReturnType.Name}");
}
}
}
カスタム属性の実装例:
csharp
[AttributeUsage(AttributeTargets.Method)]
public class ModifiedAttribute : Attribute
{
}
カスタム属性を使用したクラスの例:
csharp
public class MyClass
{
[Modified]
public string MyMethod()
{
return "Hello";
}
}
Delphiは、ネイティブコンパイルされる言語ですが、リフレクションをサポートしています。以下の例では、ユニット`Unit1`で定義されたクラス`TFoo`のインスタンスを作成し、メソッドを呼び出します。
delphi
uses Unit1, System.Rtti;
var
ctx: TRttiContext;
typ: TRttiType;
obj: TObject;
meth: TRttiMethod;
begin
ctx := TRttiContext.Create;
typ := ctx.GetType('Unit1.TFoo');
obj := typ.AsInstance.MetaclassType.Create;
meth := typ.GetMethod('hello');
meth.Invoke(obj, []);
obj.Free;
end;
まとめ
リフレクションは、プログラムの実行中に自身の構造を分析し、動的に振る舞いを変更できる強力な技術です。これにより、柔軟なプログラミングが可能になりますが、可読性やコンパイル時のエラーチェックなどの点で注意が必要です。さまざまなプログラミング言語でサポートされており、仮想マシン上で実行される言語で特に有効です。
関連項目
自己書き換えコード
* 自己反映計算 (計算機科学)