リフレクション (情報工学)

情報工学においてリフレクション (reflection) とは、プログラムの実行過程でプログラム自身の構造を読み取ったり書き換えたりする技術のことを指す。
概要.
日本語では自己言及と呼ばれる。通常リフレクションというと動的（実行時）リフレクションのことを指すが、静的（コンパイル時）リフレクションをサポートするプログラミング言語もある。リフレクションはSmalltalk、Java、.NET Frameworkのような仮想機械やインタプリタ上で実行されることを想定した言語でサポートされることが多く、C言語のような機械語として出力されることを想定した言語でサポートされることは少ない。
一般に、リフレクションとはオブジェクトがそれ自身の構造や計算上の意味を取得することを可能にするものである。リフレクションによるプログラミングパラダイムを"リフレクティブプログラミング" ("reflective programming") という。
通常、プログラムのソースコードがコンパイルされると、プログラムの構造などの情報は低レベルコード（アセンブリ言語など）に変換される過程で失われてしまう。リフレクションをサポートする場合、そのような情報は生成されるコードの中にメタデータとして保存される。
LISPやForthなど実行時とコンパイル時の区別のない言語では、コードの解釈とリフレクションとの間に違いはない。
例.
Java.
次のコードはcodice_1パッケージを使ったJava 6以降での例である。
// リフレクションなし
Foo foo = new Foo();
foo.hello();
// リフレクション
Class cl = Class.forName("Foo");
Method method = cl.getMethod("hello");
method.invoke(cl.newInstance());
どちらのコードでもcodice_2クラスのインスタンスを作成し、そのインスタンスのcodice_3メソッドを呼んでいる。
前者の例では、クラス名やメソッド名がハードコーディングされているので実行時に他のクラスに変更するのは不可能である。リフレクションを用いた後者の例では、それらを実行時に容易に変更することができる。しかしその一方で、後者は読みにくく、またコンパイル時チェックの恩恵も得られない。つまり、もしcodice_2クラスが存在しなかったとしたら前者のコードではコンパイル時にエラーとなるが、後者のコードでは実行するまでエラーが発生しない。
Perl.
次のコードは同じ例をPerlで書いたものである。
Foo->new->hello();
my $class = "Foo";
my $method = $class->can("hello");
$class->new->$method();
Objective-C.
次のコードは同じ例をObjective-Cで書いたものである。
// リフレクションなし
[[[Foo alloc] init] hello];
// リフレクション
id aClass = objc_getClass("Foo");
SEL aSelector = NSSelectorFromString(@"hello");
objc_msgSend([[aClass alloc] init], aSelector, nil);
ActionScript.
次の例は同じ例を[[ActionScript]]で書いたものである。
// リフレクションなし
var foo:Foo = new Foo();
foo.hello();
// リフレクション
var ClassReference:Class = flash.utils.getDefinitionByName("Foo") as Class;
var instance:Object = new ClassReference();
instance.hello();
JavaScript.
次の例は同じ例を[[JavaScript]]で書いたものである。
// リフレクションなし
var foo = new Foo();
foo.hello();
// リフレクション
var foo = this['Foo'];
var methodName = 'hello';
(new foo())[methodName]();
// Reflectオブジェクトを使用
const foo = Reflect.construct(Foo)
const hello = Reflect.get(foo, 'hello')
Reflect.apply(hello, foo, [])
Ruby.
次の例は同じ例を[[Ruby]]で書いたものである。
foo = Foo.new
foo.hello
foo_class = Object.const_get 'Foo'
foo = foo_class.new
foo.send 'hello'
Python.
次の例は同じ例を[[Python]]で書いたものである。
obj = Foo()
obj.hello()
class_name = "Foo"
method = "hello"
obj = globals()[class_name]()
getattr(obj, method)()
eval("Foo().hello()")
PHP.
次の例は同じ例を[[PHP (プログラミング言語)|PHP]]で書いたものである。
// リフレクションなし
$foo = new Foo();
$foo->hello();
// リフレクション
$reflector = new ReflectionClass('Foo');
$foo = $reflector->newInstance();
$hello = $reflector->getMethod('hello');
$hello->invoke($foo);
// コールバックの使用
$foo = new Foo();
call_user_func(array($foo, 'hello'));
// 可変変数構文の使用
$className = 'Foo';
$foo = new $className();
$method = 'hello';
$foo->$method();
C#.
次の例は[[C Sharp|C#]]による例で、より進んだリフレクションの用法を示している。プログラムは[[コマンドラインインタプリタ|コマンドライン]]から[[アセンブリ (共通言語基盤)|アセンブリ]]名を入力としてとる。アセンブリとは[[ライブラリ|クラスライブラリ]]のようなものである。アセンブリが読み込まれると、アセンブリ内で定義されたメソッドを検索するためにリフレクションが用いられる。見つかった各メソッドに対し、最近変更があったかどうかをリフレクションを使って調べている。もし変更があり、かつ引数をとらないメソッドであれば、メソッド名と戻り値の型を表示する。
最近変更されたかどうかを調べるために、開発者はカスタム[[メタデータ (共通言語基盤)#属性|属性]]を使う必要がある。
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using Recent;
namespace Reflect
 class Program
 private Assembly a;
 Program(String assemblyName)
 a = Assembly.Load(new AssemblyName(assemblyName));
 // 指定されたアセンブリにSupportsRecentlyModified属性が適用されていることを確認。
 Attribute c = Attribute.GetCustomAttribute(a, typeof(SupportsRecentlyModifiedAttribute));
 if (c == null)
 // "SupportsRecentlyModified"属性の取得に失敗。
 // つまり、アセンブリは"RecentlyModified"属性をサポートしていない。
 throw new Exception("アセンブリ" + assemblyName + " は必要な属性をサポートしていません。");
 Console.WriteLine("読み込み完了: " + a.FullName);
 public void FindNewMethodsWithNoArgs()
 // アセンブリに定義されている型をすべて取得する
 Type[] t = a.GetTypes();
 foreach (Type type in t)
 // この型がクラスでなければスキップ。
 if (!type.IsClass)
 continue;
 Console.WriteLine("クラス名: " + type.FullName);
 MethodInfo[] methods = type.GetMethods();
 foreach (MethodInfo method in methods)
 object[] ab = method.GetCustomAttributes(typeof(RecentlyModifiedAttribute), true);
 // 属性がひとつも取得できなければ、このメソッドは古いということである。
 if (ab.Length != 0)
 // そうでなければただ1つの属性のみ取得される。
 // なぜなら、"RecentlyModified"属性は他の属性との併用ができないからである。
 Console.Write("\t更新されたメソッド: " + method.Name);
 if (method.GetParameters().Length > 0)
 break;
 // 開発者が指定したコメントを取得するために、
 // "RecentlyModifiedAttribute"属性のインスタンスを用いる。
 Console.WriteLine("\t" + (ab[0] as RecentlyModifiedAttribute).comment);
 Console.WriteLine("\t\t戻り値: " + method.ReturnType.Name);
 static void Main(string[] args)
 try
 Program reflector = new Program("UseAttributes");
 reflector.FindNewMethodsWithNoArgs();
 catch (Exception e)
 Console.Error.WriteLine(e.Message);
上で使用したカスタム属性の実装を次に示す。
using System;
using System.Collections.Generic;
using System.Text;
namespace Recent
 // この属性はメソッドにしか適用できず、また他の属性との併用もできない。
 [AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited=true)]
 public class RecentlyModifiedAttribute : Attribute
 // この属性はメソッドに対して適用される。
 // 引数なしで([RecentlyModified])、
 // またはコメントとともに([RecentlyModified(comment="<someComment>")])適用できる。
 private String Comment = "このメソッドは最近変更されました。";
 public RecentlyModifiedAttribute()
 // 属性のインスタンス化のための空コンストラクタ。
 // 必須な引数はないのでコンストラクタは空。
 // 省略可能な引数"comment"の定義。属性が使用される際に名前つき引数として指定される。
 public String comment
 get
 return Comment;
 set
 Comment = comment;
 [AttributeUsage(AttributeTargets.Assembly, AllowMultiple=false)]
 public class SupportsRecentlyModifiedAttribute : Attribute
 // この属性は引数なしでアセンブリに適用される。
 // as in [SupportsRecentlyModified]
 public SupportsRecentlyModifiedAttribute()
 // 必須な引数はないのでコンストラクタは空。
また、このカスタム属性を使用したクラスの定義例を次に示す。
using System;
using System.Collections.Generic;
using System.Text;
using Recent;
// アセンブリが "RecentlyModified"属性をサポートすることを示すために
// "SupportsRecentlyModified"属性を適用する。
[assembly: SupportsRecentlyModified]
namespace Reflect
 class UseAttributes
 private Object info;
 public UseAttributes()
 info = (object) "Hello World";
 public void OldMethodWithNoArgs()
 Console.WriteLine("これは引数をとらない古いメソッドである。");
 // メソッドが最近変更されたということを示すために"RecentlyModified"属性を適用する。
 [RecentlyModified]
 public void NewMethodWithNoArgs()
 Console.WriteLine("これは引数をとらない新しいメソッドである。");
 public void OldMethodWithOneArg(object something)
 info = something;
 Console.WriteLine("これは引数を1つとる古いメソッドである。");
 [RecentlyModified]
 public void NewMethodWithOneArg(object something)
 info = something;
 Console.WriteLine("これは引数を1つとる新しいメソッドである。");
Delphi.
次の例は同じ例を[[Delphi]]で書いたものである。クラス TFoo はユニット Unit1 で定義されているものとする。
uses RTTI, Unit1;
// リフレクションなし
procedure WithoutReflection;
var
 Foo: TFoo;
begin
 Foo := TFoo.Create;
 try
 Foo.Hello;
 finally
 Foo.Free;
 end;
end;
// リフレクション
procedure WithReflection;
var
 RttiContext: TRttiContext;
 RttiType: TRttiInstanceType;
 Foo: TObject;
begin
 RttiType := RttiContext.FindType('Unit1.TFoo') as TRttiInstanceType;
 Foo := RttiType.GetMethod('Create').Invoke(RttiType.MetaclassType, []).AsObject;
 try
 RttiType.GetMethod('Hello').Invoke(Foo, []);
 finally
 Foo.Free;
 end;
end;
Delphiはアンマネージドでネイティブコンパイルされる言語であるため、注目に値する例となっている。リフレクションをサポートする言語の多くはPerlやPython、PHPのような[[動的プログラミング言語]]または[[スクリプト言語]]であるか、あるいはJavaやC#のようにランタイムを必要とする言語である。
関連項目.
[[Category:プログラミング]]
[[Category:プログラミングパラダイム]]