Article de reference

Programmation réflexive

En informatique , la programmation réflexive ou la réflexion est la capacité d'un processus à examiner, à introspecter et à modifier sa propre structure et son propre comporteme...

informatique , la programmation réflexive ou la réflexion est la capacité d'un processus à examiner, à introspecter et à modifier sa propre structure et son propre comportement.

langages assembleur natifs , intrinsèquement réflexifs, car ces architectures d'origine pouvaient être programmées en définissant les instructions comme des données et en utilisant du code auto-modifiable . Avec le passage massif à des langages compilés de plus haut niveau tels qu'ALGOL , COBOL , Fortran , Pascal et C , cette capacité de réflexion a largement disparu jusqu'à l'apparition de nouveaux langages de programmation intégrant la réflexion dans leurs systèmes de types.La thèse de doctorat de Brian Cantwell Smith de 1982 a introduit la notion de réflexion computationnelle dans les langages de programmation procéduraux et la notion d' interpréteur méta-circulaire comme composant de 3- Lisp .

Utilisations

La réflexion aide les programmeurs à créer des bibliothèques logicielles génériques pour afficher des données, traiter différents formats de données, effectuer la sérialisation et la désérialisation de données pour la communication, ou encore le regroupement et le dégroupage de données pour des conteneurs ou des flux de communication. Les systèmes réflexifs peuvent fournir un type miroir , utilisé comme reflet découplé d'un objet ou d'un identifiant.

L'utilisation efficace de la réflexion nécessite presque toujours un plan : un cadre de conception, une description de l'encodage, une bibliothèque d'objets, une carte d'une base de données ou des relations entre entités.

La réflexion rend un langage plus adapté au code orienté réseau. Par exemple, elle permet à des langages comme Java de fonctionner efficacement sur les réseaux en fournissant des bibliothèques pour la sérialisation, le regroupement et la gestion de formats de données variés. Les langages sans réflexion, tels que le C, doivent utiliser des compilateurs auxiliaires pour des tâches comme la notation syntaxique abstraite ( ASN) afin de générer le code nécessaire à la sérialisation et au regroupement.

La réflexion permet d'observer et de modifier l'exécution d'un programme en temps réel . Un composant de programme orienté réflexion peut surveiller l'exécution d'un bloc de code et se modifier en fonction de l'objectif visé par ce bloc. Ceci est généralement réalisé en assignant dynamiquement du code au cours de l'exécution.

Dans les langages de programmation orientés objet comme Java , la réflexion permet d'inspecter les classes, les interfaces, les champs et les méthodes à l'exécution sans connaître leurs noms à la compilation . Elle permet également l'instanciation de nouveaux objets et l'appel de méthodes.

La réflexion est souvent utilisée dans le cadre des tests logiciels , notamment pour la création/instanciation à l'exécution d' objets factices .

La réflexion est également une stratégie clé pour la métaprogrammation .

Dans certains langages de programmation orientés objet comme C# et Java , la réflexion permet de contourner les règles d'accessibilité des membres . Pour les propriétés C#, cela s'effectue en écrivant directement dans le champ de stockage (généralement invisible) d'une propriété non publique. Il est également possible de trouver les méthodes non publiques des classes et des types et de les appeler manuellement. Ceci fonctionne aussi bien pour les fichiers internes au projet que pour les bibliothèques externes telles que les assemblys .NET et les archives Java.

Mise en œuvre

du code source (blocs de code, classes , méthodes, protocoles, etc.) comme des objets de première classe à l'exécution . Les métadonnées lisibles des objets sont fournies sous forme de miroir .
  • Convertir une chaîne correspondant au nom symbolique d'une classe ou d'une fonction en une référence ou un appel à cette classe ou fonction.
  • Évaluer une chaîne de caractères comme s'il s'agissait d'une instruction de code source lors de l'exécution.
  • Créer un nouvel interpréteur pour le bytecode du langage afin de donner une nouvelle signification ou un nouvel objectif à une construction de programmation.
  • Ces fonctionnalités peuvent être implémentées de différentes manières. En programmation orientée objet ( MOO) , la réflexion fait partie intégrante du langage de programmation courant. Lors de l'appel de verbes (méthodes), diverses variables, telles que verble nom du verbe appelé et thisl'objet sur lequel il est appelé, sont initialisées afin de fournir le contexte de l'appel. La sécurité est généralement gérée par l'accès programmatique à la pile d'appel : cette pile contenant callers()la liste des méthodes ayant finalement appelé le verbe courant, l'exécution de tests sur callers()[0]la commande invoquée par l'utilisateur initial permet au verbe de se protéger contre toute utilisation non autorisée.

    Les langages compilés s'appuient sur leur environnement d'exécution pour obtenir des informations sur le code source. Un exécutable Objective-C compilé , par exemple, enregistre les noms de toutes les méthodes d'un bloc, fournissant une table permettant de les associer aux méthodes sous-jacentes (ou aux sélecteurs de ces méthodes) compilées dans le programme. Dans un langage compilé prenant en charge la création de fonctions à l'exécution, tel que Common Lisp , l'environnement d'exécution doit inclure un compilateur ou un interpréteur.

    La réflexion peut être implémentée pour les langages dépourvus de réflexion intégrée en utilisant un système de transformation de programmes pour définir des modifications automatisées du code source.

    Considérations de sécurité

    La réflexion peut permettre à un utilisateur de créer des chemins d'exécution inattendus au sein d'une application, contournant potentiellement les mesures de sécurité. Cette vulnérabilité peut être exploitée par des attaquants. Des failles historiques dans Java, dues à une réflexion non sécurisée, permettaient à du code récupéré depuis des machines distantes potentiellement non fiables de s'échapper du mécanisme de sécurité du bac à sable Java . Une étude à grande échelle portant sur 120 vulnérabilités Java en 2013 a conclu que la réflexion non sécurisée est la vulnérabilité la plus courante dans Java, bien que ce ne soit pas la plus exploitée.

    performances d'exécution

    Les systèmes de réflexion à l'exécution introduisent une surcharge de performance non négligeable . Par exemple, en Java , les opérations de réflexion étant résolues dynamiquement à l'exécution, de nombreuses optimisations du compilateur et de la JVM (comme l'inclusion de méthodes , la liaison statique et la spécialisation JIT agressive) ne peuvent être pleinement appliquées. Par conséquent, les appels réflexifs sont généralement plus lents que leurs équivalents résolus statiquement. Des études de microbenchmarks et d'applications sur Java ont montré que les opérations réflexives peuvent engendrer une surcharge d'exécution substantielle, notamment pour l'appel de méthodes et la création dynamique d'objets, avec des ralentissements allant de 20 à 40 % dans les cas modérés à plus de 300 fois dans les scénarios de dispatch fortement réflexifs. Dans les applications séquentielles, la réflexion peut augmenter considérablement le temps d'exécution et la consommation de mémoire lorsqu'elle est utilisée dans des chemins de code critiques en termes de performances. Dans les applications multithreadées , les implémentations réflexives préservent généralement l'évolutivité , mais présentent tout de même des temps d'exécution absolus sensiblement plus élevés — généralement entre 1,5x et 10x plus lents — que le code non réflexif équivalent.

    Dans des langages comme C++ et Rust , la réflexion est effectuée à la compilation . Les systèmes de réflexion à la compilation sont moins performants que les systèmes de réflexion à l'exécution comme ceux de Java et C#, mais n'entraînent aucune surcharge à l'exécution puisqu'ils sont effectués lors de la compilation.

    Exemples

    Les extraits de code suivants créent une instanceclasseméthodelangage de programmation , les séquences d'appel classiques et par réflexion sont présentées.

    Common Lisp

    Voici un exemple en Common Lisp utilisant le système d'objets Common Lisp :

    f ));; Normal, sans réflexion ( let (( foo ( make-instance 'foo ))) ( print-hello foo ));; Par réflexion, rechercher la classe nommée « foo » et la méthode ;; nommée « print-hello » qui se spécialise sur « foo ». ( 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

    La réflexion n'est pas possible en C , bien que certaines parties de la réflexion puissent être émulées.

    " ); }// Type de structure de table de méthodes simulée { const char * nom ; Méthode fn ; } MethodEntry ;const MethodEntry FOO_METHODS [] = { { "printHello" , Foo_printHello }, { NULL , NULL } // Sentinelle pour marquer la fin };// Simuler la recherche de méthode par réflexion 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 () { // Sans réflexion Foo foo ; Foo_printHello ( & foo );// Avec réflexion émulée Foo * reflected = ( Foo * ) malloc ( sizeof ( Foo )); if ( ! reflected ) { fprintf ( stderr , "Échec de l'allocation mémoire " ); return EXIT_FAILURE ; }const char * nom = "printHello" ; Méthode m = trouverMethodByName ( nom );if ( m ) { m ( reflected ); } else { fprintf ( stderr , "Méthode '%s' introuvable " , name ); free ( reflected ); return EXIT_FAILURE ; }libre ( réfléchi ) ; retourner EXIT_SUCCESS ; }

    C++

    Voici un exemple en C++ (utilisant la réflexion ajoutée dans C++26 ).

    , 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 ) } ); }classe Foo { privé : // ... public : Foo () = par défaut ;void printHello () const { std :: println ( "Bonjour, monde !" ); } };int main ( int argc , char * argv []) { Foo foo ;// Sans réflexion foo . printHello ();// Avec réflexion auto invokePrint = [ : createInvoker ( ^^ Foo , "printHello" ) : ]; invokePrint ( foo );retourner 0 ; }

    C#

    Voici un exemple en C# :

    Bonjour, monde !" ) ; } }public class InvokeFooExample { static void Main ( string [] args ) { // Sans réflexion Foo foo = new (); foo . PrintHello ();// Avec réflexion Object reflectedFoo = Activator.CreateInstance ( typeof ( Foo ) ); MethodInfo method = reflectedFoo.GetType () . GetMethod ( " PrintHello " ) ; method.Invoke ( foo , null ) ; } }

    Delphi, Object Pascal

    Cet exemple Delphi et Object Pascal suppose qu'une classe TFoo a été déclarée dans une unité appelée Unit1 :); Instance foo = eInstance_New ( fooClass ); Method m = eClass_FindMethod ( fooClass , "hello" , fooClass . module ); (( void ( * )())( void * ) m . function )( foo );

    Aller

    Voici un exemple en Go :

    "reflect" )type Foo struct {}func ( f Foo ) Hello () { fmt . Println ( "Bonjour, monde !" ) }func main ( ) { // Sans réflexion var f Foo f.Hello ( )// Avec réflexion var fT reflect.Type = reflect.TypeOf ( Foo { } ) var fV reflect.Value = reflect.New ( fT )var m reflect.Value = fV.MethodByName ( " Bonjour " )if m.IsValid ( ) { m.Call ( nil ) } else { fmt.Println ( " Méthode introuvable " ) } }

    Java

    Voici un exemple en Java :

    Bonjour, monde ! " ) ; } }public class InvokeFooExample { public static void main ( String [] args ) { // Sans réflexion Foo foo = new Foo (); foo . printHello ();// Avec réflexion, essayez { Foo reflectedFoo = Foo . class . getDeclaredConstructor () . newInstance ();Méthode m = reflectedFoo.getClass () . getDeclaredMethod ( "printHello" , new Class < ?>[ 0 ] ) ; m.invoke ( reflectedFoo ) ; } catch ( ReflectiveOperationException e ) { System.err.printf ( " Une erreur s'est produite : % s % n " , e.getMessage ( ) ) ; } } }

    Java fournit également une classe interne (non officiellement incluse dans la bibliothèque de classes Java ) dans le modulejdk.unsupported `java.json` , sun.reflect.Reflectionutilisée par ` java.json` sun.misc.Unsafe. Elle contient une méthode `get` permettant d'obtenir la classe effectuant un appel à une profondeur spécifiée. Cette classe est désormais remplacée par la classe `java.json` et sa méthode ` get` .JavaScript :

    TypeScript :

    void = Reflect.get(foo, 'hello') as (this: Foo) => void; Reflect.apply(hello, foo, []); // With eval eval('new Foo().hello()'); "
    importer 'reflect-metadata' ;// Sans réflexion const foo : Foo = new Foo (); foo . hello ();// Avec réflexion const foo : Foo = Reflect . construct ( Foo ); const hello : ( this : Foo ) => void = Reflect . get ( foo , 'hello' ) as ( this : Foo ) => void ; Reflect . apply ( hello , foo , []);// Avec eval eval ( 'new Foo().hello()' );

    Julia

    Voici un exemple en Julia :

    struct Point x::Int y end # Inspection with reflection julia> fieldnames(Point) (:x, :y) julia> fieldtypes(Point) (Int64, Any) julia> p = Point(3,4) # Access with reflection julia> getfield(p, :x) 3 "
    julia> struct Point x :: Int y end# Inspection avec réflexion julia> fieldnames ( Point ) (:x, :y)julia> fieldtypes ( Point ) (Int64, Any)julia> p = Point ( 3 , 4 )# Accès par réflexion julia> getfield ( p , :x ) 3

    Kotlin

    Utilisation de la réflexion Java :

    ) } }fun main ( args : Array <String> ) { // Sans réflexion val foo = Foo ( ) foo.printHello ( )// Avec réflexion, essayez { // Foo::class.java récupère un java.lang.Class<Foo> val reflectedFoo = Foo :: class . java . getDeclaredConstructor () . newInstance ()val m : Méthode = reflectedFoo . javaClass . getDeclaredMethod ( "printHello" )m.invoke ( reflectedFoo ) } catch ( e : ReflectiveOperationException ) { System.err.printf ( " Une erreur s'est produite : % s % n " , e.message ) } }

    Utilisation de Kotlin pur :

    ) } }fun main ( args : Array <String> ) { // Sans réflexion val foo = Foo ( ) foo.printHello ( )// Avec réflexion 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 ( "Une erreur s'est produite : %s%n" , e . message ) } }

    Objectif-C

    Voici un exemple en Objective-C , impliquant l' utilisation du framework OpenStep ou Foundation Kit :

    ) alloc ] init ]; [ obj performSelector : @selector ( hello )];

    Perl

    Voici un exemple en Perl :

    new; $foo->hello; # or Foo->new->hello; # With reflection my $class = \"Foo\" my $constructor = \"new\"; my $method = \"hello\"; my $f = $class->$constructor; $f->$method; # or $class->$constructor->$method; # with eval eval \"new Foo->hello;\"; "
    # Sans réflexion, mon $foo = Foo -> nouveau ; $foo -> bonjour ;# ou Foo -> nouveau -> bonjour ;# Par réflexion, ma $class = "Foo" ma $constructeur = "new" ; ma $méthode = "hello" ;mon $f = $class -> $constructeur ; $f -> $méthode ;# ou $classe -> $constructeur -> $méthode ;# avec eval eval "new Foo->hello;" ;

    PHP

    Voici un exemple en PHP :

    hello(); // With reflection, using Reflections API $reflector = new ReflectionClass(\"Foo\"); $foo = $reflector->newInstance(); $hello = $reflector->getMethod(\"hello\"); $hello->invoke($foo); "
    // Sans réflexion $foo = new Foo (); $foo- > hello ();// Avec réflexion, en utilisant l'API Reflections $reflector = new ReflectionClass ( "Foo" ); $foo = $reflector -> newInstance (); $hello = $reflector -> getMethod ( "hello" ); $hello -> invoke ( $foo );

    Python

    Voici un exemple en Python :

    None: print(\"Hello, world!\") if __name__ == \"__main__\": # Without reflection obj: Foo = Foo() obj.print_hello() # With reflection obj: Foo = globals()[\"Foo\"]() _: Any = getattr(obj, \"print_hello\")() # With eval eval(\"Foo().print_hello()\") "
    from typing import Anyclasse Foo : # ... def print_hello ( self ) -> None : print ( "Bonjour, monde !" )if __name__ == "__main__" : # Sans réflexion obj : Foo = Foo () obj . print_hello ()# Avec réflexion obj : Foo = globals ()[ "Foo" ]() _ : Any = getattr ( obj , "print_hello" )()# Avec eval eval ( "Foo().print_hello()" )

    R

    Voici un exemple en R :

    generic_having_foo_method <- "hello" obj <- do.call ( class_name , list ()) do.call ( generic_having_foo_method , alist ( obj ))

    Rubis

    Voici un exemple en Ruby :

    Foo " ) . new obj.send : hello# Avec eval eval "Foo.new.hello"

    Rouiller

    Voici un exemple en Rust (utilisant des macros procédurales ).

    { match $method_name { \"print_hello\" => $instance.print_hello(), _ => eprintln!(\"An error occurred: Method not found.\"), } }; } struct Foo; impl Foo { fn print_hello(&self) { println!(\"Hello, world!\"); } } fn main() { let foo = Foo; // Normal call sequence foo.print_hello(); // Reflection-style sequence via compile-time mapping let method_to_call = \"print_hello\"; invoke_method!(foo, method_to_call); } "
    // Une macro de base qui associe une chaîne de caractères représentant le nom d'une méthode à un appel de méthode réel. macro_rules! invoke_method { ( $instance : expr , $method_name : expr ) => { match $method_name { "print_hello" => $instance . print_hello (), _ => eprintln! ( "Une erreur s'est produite : Méthode introuvable." ), } }; }struct Foo ;impl Foo { fn print_hello ( & self ) { println! ( "Bonjour, monde !" ); } }fn main () { let foo = Foo ;// Séquence d'appel normale foo . print_hello ();// Séquence de style réflexion via un mappage à la compilation let method_to_call = "print_hello" ; invoke_method ! ( foo , method_to_call ); }

    Rapide

    La réflexion Swift est en lecture seule, et l'invocation dynamique n'est donc possible que via Objective-C .

    ) } }class InvokeFooExample { static func main () { // Sans réflexion let foo = Foo () foo . printHello ()// Avec la « réflexion » (environnement d'exécution Objective-C) , soit reflectedFoo = Foo ()let selector = #selector ( Foo . printHello )si reflectedFoo.respond ( à : selector ) { reflectedFoo.perform ( selector ) } } }

    Le système de réflexion de Swift, avec Swift.Mirror, est beaucoup plus limité dans sa portée :

    ) } }soit foo = Foo ()soit miroir = Miroir ( réfléchissant : foo )print ( type ( of : foo ) ) // Foo print ( mirror.children.count ) // Propriétés uniquement

    Xojo

    Voici un exemple utilisant Xojo :

    PrintHello " Then m.Invoke ( fooInstance ) End If Next

    Plus d articles de Worldlex Wiki

    Revenez a l index pour explorer davantage de pages sur l histoire, la science, la culture, la geographie et la societe en francais.

    Explorer l index