コンピュータサイエンスにおいて、リフレクティブプログラミングまたはリフレクションとは、プロセスが自身の構造と動作を検査、内省、修正する能力のことである。 [ 1 ]
初期のコンピュータは、独自のアセンブリ言語でプログラミングされていました。これらの初期のアーキテクチャは、命令をデータとして定義し、自己修正コードを使用することでプログラミングできたため、アセンブリ言語は本質的に反射的な性質を持っていました。プログラミングの大部分がALGOL、COBOL、Fortran、Pascal、Cなどの高水準コンパイル言語に移行するにつれて、この反射的な機能はほぼ失われましたが、型システムに反射機能を組み込んだ新しいプログラミング言語が登場しました。
ブライアン・カントウェル・スミスの1982年の博士論文は、手続き型プログラミング言語における計算的リフレクションの概念と、3- Lispの構成要素としてのメタ循環インタプリタの概念を導入した。[ 2 ] [ 3 ]
リフレクションは、プログラマが汎用的なソフトウェアライブラリを作成し、データの表示、さまざまな形式のデータの処理、通信のためのデータのシリアル化と逆シリアル化、コンテナや通信バーストのためのデータのバンドルとアンバンドルを行うのに役立ちます。リフレクティブシステムは、オブジェクトまたは識別子の分離型リフレクションとして使用されるミラータイプを提供する場合があります。
効果的なリフレクションの利用には、ほぼ必ず計画が必要です。設計フレームワーク、エンコーディング記述、オブジェクトライブラリ、データベースマップ、またはエンティティ関係などです。
リフレクションは、ネットワーク指向のコードに適した言語を実現します。例えば、Javaのような言語は、シリアライゼーション、バンドル、多様なデータ形式のためのライブラリを利用できるため、ネットワーク上でスムーズに動作します。C言語のようにリフレクションを持たない言語では、シリアライゼーションやバンドル用のコードを生成するために、抽象構文記法( ASN.1)などの補助コンパイラを使用する必要があります。
リフレクションは、実行時にプログラムの実行を監視したり変更したりするために使用できます。リフレクション指向のプログラムコンポーネントは、コードの囲み領域の実行を監視し、その囲み領域の目的に応じて自身を変更できます。これは通常、実行時にプログラムコードを動的に割り当てることによって実現されます。
Javaのようなオブジェクト指向プログラミング言語では、リフレクションを用いることで、コンパイル時にインターフェース、フィールド、メソッドの名前を知らなくても、実行時にクラス、インターフェース、フィールド、メソッドを検査できます。また、新しいオブジェクトのインスタンス化やメソッドの呼び出しも可能です。
リフレクションは、ソフトウェアテストの一部としてよく使用され、例えば、実行時にモックオブジェクトを作成/インスタンス化する場合などに用いられます。
内省はメタプログラミングにおける重要な戦略でもある。
C#やJavaなどのオブジェクト指向プログラミング言語では、リフレクションを使用してメンバのアクセス制限を回避できます。C#のプロパティの場合、非公開プロパティの(通常は非表示の)バッキングフィールドに直接書き込むことでこれを実現できます。また、クラスや型の非公開メソッドを見つけて手動で呼び出すことも可能です。これは、プロジェクト内部ファイルだけでなく、.NETのアセンブリやJavaのアーカイブなどの外部ライブラリにも適用できます。
リフレクションをサポートする言語は、低レベル言語では実現が難しい多くの機能をランタイム時に提供します。これらの機能には、次のようなものがあります。
これらの機能はさまざまな方法で実装できます。MOO では、リフレクションは日常的なプログラミングの慣習の自然な一部となっています。動詞 (メソッド) が呼び出されると、verb(呼び出される動詞の名前) やthis(動詞が呼び出されるオブジェクト) などのさまざまな変数が設定され、呼び出しのコンテキストが提供されます。セキュリティは通常、呼び出し元のスタックにプログラムでアクセスすることによって管理されます。 はcallers()現在の動詞が最終的に呼び出されたメソッドのリストであるため、callers()[0](元のユーザーによって呼び出されたコマンド) に対してテストを実行することで、動詞は不正使用から自身を保護できます。
コンパイル言語は、ソースコードに関する情報をランタイムシステムから得る必要があります。例えば、コンパイルされたObjective-C実行ファイルは、実行ファイルのブロック内のすべてのメソッド名を記録し、それらをプログラムにコンパイルされた基となるメソッド(またはこれらのメソッドのセレクタ)と対応付けるテーブルを提供します。Common Lispのように、実行時に関数を生成するコンパイル言語では、ランタイム環境にコンパイラまたはインタプリタが含まれている必要があります。
リフレクション機能が組み込まれていない言語でも、プログラム変換システムを用いてソースコードの変更を自動化することで、リフレクション機能を実装できます。
リフレクションによって、ユーザーはアプリケーションを通して予期しない制御フローパスを作成でき、セキュリティ対策を回避できる可能性があります。これは攻撃者によって悪用される可能性があります。 [ 7 ]安全でないリフレクションによって引き起こされた Java の過去の脆弱性により、信頼できない可能性のあるリモート マシンから取得したコードが Javaサンドボックスセキュリティ メカニズムから抜け出すことができました。2013 年に 120 の Java 脆弱性に関する大規模な調査では、安全でないリフレクションは Java で最も一般的な脆弱性であるものの、最も悪用されている脆弱性ではないと結論付けられました。[ 8 ]
実行時リフレクションシステムは、無視できない実行時パフォーマンスのオーバーヘッドをもたらします。たとえば、Javaでは、リフレクション操作は実行時に動的に解決されるため、メソッドのインライン化、静的バインディング、積極的なジャストインタイム特殊化など、多くのコンパイラおよびJVM の最適化を完全に適用することはできません。その結果、リフレクション呼び出しは通常、静的に解決される呼び出しよりも遅くなります。Javaのマイクロベンチマークおよびアプリケーションレベルの研究では、リフレクション操作は、特にメソッド呼び出しと動的オブジェクト作成において、かなりの実行時オーバーヘッドを引き起こす可能性があり、中程度のケースでは約 20 ~ 40% の速度低下、リフレクションディスパッチが多用されるシナリオでは 300 倍以上の速度低下が発生することが示されています。[ 9 ]シーケンシャルアプリケーションでは、パフォーマンスが重要なコードパスで使用される場合、リフレクションによって実行時間とメモリ消費が大幅に増加する可能性があります。マルチスレッドアプリケーションでは、リフレクション実装は一般的にスケーラビリティを維持しますが、同等の非リフレクションコードと比較して、絶対実行時間は著しく長くなり、一般的に 1.5 倍から 10 倍遅くなります。[ 9 ]
C++やRustのような言語では、リフレクションはコンパイル時に実行されます。コンパイル時のリフレクションシステムは、JavaやC#のような実行時のリフレクションシステムよりも弱いですが、コンパイル中に実行されるため、実行時のオーバーヘッドは発生しません。[ 10 ]
以下のコードスニペットは、クラスのインスタンス を作成し、そのメソッドを呼び出します。各プログラミング言語について、通常の呼び出しシーケンスとリフレクションに基づく呼び出しシーケンスを示します。fooFooPrintHello
以下の例は、Common Lispオブジェクトシステムを使用したCommon Lispのコードです。
( defclass foo () ()) ( defmethod print-hello (( f foo )) ( format T "Hello from ~S~%" f ));; 通常、リフレクションなし( let (( foo ( make-instance 'foo ))) ( print-hello foo ));; リフレクションを使用して「foo」という名前のクラスと、「foo」に特化した「print-hello」という名前のメソッドを検索します。( let* (( foo-class ( find-class ( read-from-string "foo" ))) ( print-hello-method ( find-method ( symbol-function ( read-from-string "print-hello" )) nil ( list foo-class )))) ( funcall ( sb-mop:method-generic-function print-hello-method ) ( make-instance foo-class )))C言語ではリフレクションは不可能ですが、リフレクションの一部をエミュレートすることは可能です。
#include <stdio.h> #include <stdlib.h> #include <string.h>typedef struct { // ... } Foo ;typedef void ( *メソッド)( void * );// メソッド: Foo::printHello void Foo_printHello ( void * _ ) { printf ( "Hello, world! \n " ); }// シミュレーションされたメソッド テーブルtypedef struct { const char * name ; Method fn ; } MethodEntry ;const MethodEntry FOO_METHODS [] = { { "printHello" , Foo_printHello }, { NULL , NULL } // 終了を示す番兵};// リフレクティブなメソッド検索をシミュレートするMethod findMethodByName ( const char * name ) { for ( size_t i = 0 ; FOO_METHODS [ i ]. name ; i ++ ) { if ( strcmp ( FOO_METHODS [ i ]. name , name ) == 0 ) { return FOO_METHODS [ i ]. fn ; } } return NULL ; }int main () { // リフレクションなしFoo foo ; Foo_printHello ( & foo );// エミュレートされたリフレクションを使用する場合Foo * reflected = ( Foo * ) malloc ( sizeof ( Foo )); if ( ! reflected ) { fprintf ( stderr , "メモリ割り当てに失敗しました\n " ); return EXIT_FAILURE ; }const char * name = "printHello" ; Method m = findMethodByName ( name );if ( m ) { m ( reflected ); } else { fprintf ( stderr , "メソッド '%s' が見つかりません\n " , name ); free ( reflected ); return EXIT_FAILURE ; }free ( reflected ); return EXIT_SUCCESS ; }以下の例はC++で記述されています( C++26で追加されたリフレクションを使用)。[ 11 ]
import std ;using std :: string_view ; using std :: views :: filter ; using std :: meta :: access_context ; using std :: meta :: exception ; using std :: meta :: info ;namespace meta = std :: meta ;consteval bool isNonstaticMethod ( info mem ) noexcept { return meta :: is_class_member ( mem ) && ! meta :: is_static_member ( mem ) && meta :: is_function ( mem ); }consteval info findMethod ( info type , string_view name ) { for ( info member : meta :: members_of ( type , access_context :: current ()) | filter ( isNonstaticMethod )) { if ( meta :: has_identifier ( member ) && meta :: identifier_of ( member ) == name ) { return member ; } } // 注: これは std::meta::exception であり、std::exception ではありませんthrow exception ( std :: format ( "型 {} からメソッド {} を取得できませんでした" , name , meta :: identifier_of ( type )), ^^ findMethod ); }template < info T , const char * Name > constexpr auto createInvokerImpl = []() -> auto { static constexpr info M = findMethod ( T , Name ); contract_assert ( meta :: parameters_of ( M ). size () == 0 && meta :: return_type_of ( M ) == ^^ void ); return []([ : T : ] & instance ) -> void { instance .[ : M : ](); }; }();consteval info createInvoker ( info type , string_view name ) { return meta :: substitute ( ^^ createInvokerImpl , { meta :: reflect_constant ( type ), meta :: reflect_constant_string ( name ) } ); }class Foo { private : // ... public : Foo () = default ;void printHello () const { std :: println ( "Hello, world!" ); } };int main ( int argc , char * argv []) { Foo foo ;// リフレクションなしfoo.printHello ( ) ;// リフレクションを使用する場合auto invokePrint = [ : createInvoker ( ^^ Foo , "printHello" ) : ]; invokePrint ( foo );return 0 ; }以下の例はC#で記述されています。
名前空間Wikipedia.Examples ;using System ; using System.Reflection ;class Foo { // ...public void PrintHello () { Console.WriteLine ( " Hello, world!" ) ; } }public class InvokeFooExample { static void Main ( string [] args ) { // リフレクションなしFoo foo = new (); foo . PrintHello ();//リフレクションを使用する場合Object reflectedFoo = Activator.CreateInstance ( typeof ( Foo ) ) ; MethodInfo method = reflectedFoo.GetType () . GetMethod ( " PrintHello" ) ; method.Invoke ( foo , null ) ; } }このDelphiとObject Pascalの例では、Unit1というユニット内でTFooクラスが宣言されていることを前提としています。
RTTI 、ユニット1を使用します。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 ;以下の例はeC言語で記述されています。
// リフレクションなしFoo foo {}; foo . hello ();// リフレクションを使用Class fooClass = eSystem_FindClass ( __thisModule , "Foo" ); Instance foo = eInstance_New ( fooClass ); Method m = eClass_FindMethod ( fooClass , "hello" , fooClass . module ); (( void ( * )())( void * ) m . function )( foo );以下の例はGo言語で書かれています。
import ( "fmt" "reflect" )型Foo構造体{}func ( f Foo ) Hello () { fmt.Println ( " Hello , world! " ) }func main () { // リフレクションなしvar f Foo f . Hello ()//リフレクションを使用する場合var fT reflect.Type = reflect.TypeOf ( Foo { } ) var fV reflect.Value = reflect.New ( fT )var m reflect.Value = fV.MethodByName ( " Hello " )if m.IsValid ( ) { m.Call ( nil ) } else { fmt.Println ( "メソッドが見つかりません" ) } }以下の例はJavaで記述されています。
パッケージorg.wikipedia.examples ;import java.lang.reflect.Method ;class Foo { // ...public void printHello ( ) { System.out.println ( " Hello, world !" ) ; } }public class InvokeFooExample { public static void main ( String [] args ) { // リフレクションなしFoo foo = new Foo (); foo . printHello ();// リフレクションを使用する場合try { Foo reflectedFoo = Foo . class . getDeclaredConstructor () . newInstance ();Method m = reflectiveFoo.getClass () . getDeclaredMethod ( "printHello" , new Class <?>[ 0 ] ) ; m.invoke ( reflectiveFoo ) ; } catch ( ReflectiveOperationException e ) { System.err.printf ( " An error occurred: %s % n " , e.getMessage ( ) ) ; } } }Java には、モジュールに (公式にはJava クラス ライブラリには含まれていない)内部クラスも用意されており、これは によって使用されます。このクラスには、指定された深さで呼び出しを行うクラスを取得するためのメソッドが 1 つ含まれています。 [ 12 ]これは現在、クラスとそのメソッド を使用することで置き換えられています。jdk.unsupportedsun.reflect.Reflectionsun.misc.UnsafestaticClass<?>getCallerClass(intdepth)java.lang.StackWalker.StackFrameClass<?>getDeclaringClass()
以下の例はJuliaで記述されています。
julia> struct Point x :: Int y end# リフレクションによる検査julia> fieldnames ( Point ) (:x, :y)julia> fieldtypes ( Point ) (Int64, Any)julia> p = Point ( 3 , 4 )# リフレクションによるアクセスjulia> getfield ( p , :x ) 3Javaのリフレクションを使用する:
パッケージorg.wikipedia.examplesimport java.lang.reflect.Methodclass Foo { // ...コンストラクタ()fun printHello () { println ( "Hello, world!" ) } }fun main ( args : Array < String > ) { // リフレクションなしval foo = Foo () foo . printHello ()// リフレクションを使用する場合try { // Foo::class.java は java.lang.Class<Foo> を取得しますval reflectedFoo = Foo :: class . java . getDeclaredConstructor () . newInstance ()val m : Method = reflectedFoo . javaClass . getDeclaredMethod ( "printHello" )m.invoke ( reflectedFoo ) } catch ( e : ReflectiveOperationException ) { System.err.printf ( "エラーが発生しました: % s % n " , e.message ) } }純粋なKotlinを使用する場合:
パッケージorg.wikipedia.examplesimport kotlin.reflect.full.createInstance import kotlin.reflect.full.functionsclass Foo { // ... fun printHello () { println ( "Hello, world!" ) } }fun main ( args : Array < String > ) { // リフレクションなしval foo = Foo () foo . printHello ()// リフレクションを使用する場合try { val kClass = Foo :: class val reflectedFoo = kClass . createInstance () val function = kClass . functions . first { it . name == "printHello" } function . call ( reflectedFoo ) } catch ( e : Exception ) { System . err . printf ( "エラーが発生しました: %s%n" , e . message ) } }以下の例はObjective-Cで記述されており、OpenStepまたはFoundation Kitフレームワークのいずれかが使用されていることを示しています。
// Foo クラス。@interface Foo : NSObject - ( void ) hello ; @end// リフレクションを使用せずに Foo インスタンスに「hello」を送信します。Foo * obj = [[ Foo alloc ] init ]; [ obj hello ];// リフレクションを使用して Foo インスタンスに「hello」を送信します。id obj = [[ NSClassFromString ( @"Foo" ) alloc ] init ]; [ obj performSelector : @selector ( hello )];以下の例はPerlで記述されています。
# リフレクションなしmy $foo = Foo -> new ; $foo -> hello ;# またはFoo -> new -> hello ;# リフレクションを使用する場合my $class = "Foo" my $constructor = "new" ; my $method = "hello" ;my $f = $class -> $constructor ; $f -> $method ;# または$class -> $constructor -> $method ;# eval を使用eval "new Foo->hello;" ;// リフレクションなし$foo = new Foo (); $foo -> hello ();// リフレクションを使用する場合、Reflections API を使用します。$reflector = new ReflectionClass ( "Foo" ); $foo = $reflector -> newInstance (); $hello = $reflector -> getMethod ( "hello" ); $hello -> invoke ( $foo );以下の例はPythonで記述されています。
from typing import Anyclass Foo : # ... def print_hello ( self ) -> None : print ( "Hello, world!" )if __name__ == "__main__" : # リフレクションなしobj : Foo = Foo () obj . print_hello ()# リフレクションを使用する場合obj : Foo = globals ()[ "Foo" ]() _ : Any = getattr ( obj , "print_hello" )()# eval を使用する場合eval ( "Foo().print_hello()" )以下の例はR言語で記述されています。
# リフレクションを使用せず、foo() がメソッド "hello" を持つ S3 型のオブジェクトを返すと仮定します。obj <- foo () hello ( obj )# リフレクションを使用class_name <- "foo" generic_having_foo_method <- "hello" obj <- do.call ( class_name , list ()) do.call ( generic_having_foo_method , alist ( obj ))以下の例はRubyで記述されています。
#リフレクションなしobj = Foo.new obj.hello# リフレクションを使用obj = Object.const_get ( " Foo " ) . new obj.send : hello# eval を使用する場合eval "Foo.new.hello"以下の例はRust(手続き型マクロを使用)で記述されています。
// メソッド名文字列を実際のメソッド呼び出しに登録する基本的なマクロmacro_rules! invoke_method { ( $instance : expr , $method_name : expr ) => { match $method_name { "print_hello" => $instance . print_hello (), _ => eprintln! ( "エラーが発生しました: メソッドが見つかりません。" ), } } ; }構造体Foo ;impl Foo { fn print_hello ( & self ) { println! ( "Hello, world!" ); } }fn main () { let foo = Foo ;// 通常の呼び出しシーケンスfoo.print_hello ( ) ;// コンパイル時マッピングによるリフレクションスタイルのシーケンスlet method_to_call = "print_hello" ; invoke_method ! ( foo , method_to_call ); }Swiftのリフレクションは読み取り専用であるため、動的な呼び出しはObjective-Cを介してのみ可能です。
財団を輸入するclass Foo : NSObject { @objc func printHello () { print ( "Hello, world!" ) } }class InvokeFooExample { static func main () { // リフレクションなしlet foo = Foo () foo . printHello ()// 「リフレクション」(Objective-C ランタイム)を使用する場合let reflectedFoo = Foo ()let selector = #selector ( Foo . printHello )reflectFoo.responds ( to : selector )の場合、reflectedFoo.perform ( selector )を実行する。Swiftのリフレクションシステムは、 を使用するとSwift.Mirror、その範囲がはるかに限定されます。
財団を輸入するclass Foo { func printHello () { print ( "Hello, world!" ) } }let foo = Foo ()let mirror = Mirror ( reflecting : foo )print ( type ( of : foo ) ) // Foo print ( mirror.children.count ) //プロパティのみ以下はXojoを使用した例です。
リフレクションなしDim fooInstance As New Foo fooInstance . PrintHello' リフレクションを使用Dim classInfo As Introspection . Typeinfo = GetTypeInfo ( Foo ) Dim constructors () As Introspection . ConstructorInfo = classInfo . GetConstructors Dim fooInstance As Foo = constructors ( 0 ). Invoke Dim methods () As Introspection . MethodInfo = classInfo . GetMethods For Each m As Introspection . MethodInfo In methods If m . Name = "PrintHello" Then m . Invoke ( fooInstance ) End If Next