Les assemblages

Un article de Mangue.org, l'encyclopéde libre.

Un assemblage (ou "Assembly") désigne un ensemble de fonctionnalités auquel est attribué un numéro de version. L'utilisation des assemblages permet de régler le problème des conflits de versions et de dépendances au moment du déploiement d'une nouvelle application. Pour parler plus simplement, un assemblage est un fichier DLL ou EXE.


Sommaire

Description

Les assemblages sont en quelque sorte les composants d'un application .NET. Ils forment l'unité fondamentale de déploiement, contrôle de version, réutilisabilité et permissions. Un assemblage est en fait un ensemble de types de données et de ressources faits pour fonctionner ensemble comme un bloc indépendant. Un assemblage donne au CLR l'information dont il a besoin pour connaitre le fonctionnement des types de données utilisés. Ainsi, pour le CLR, un type de données n'existe pas en dehors de l'assemblage où il est défini.
Voyons plus en détail ce qu'apportent les assemblages.


Rôle des assemblages

Les assemblages ont un rôle prépondérant au sein de l'architecture .NET. Voyons, point par point, ce qu'ils apportent:
  • Les assemblages contiennent le code qui sera exécuté par le CLR. Un code en "Intermediate Langage" ne sera pas exécuté si aucune description de son assemblage ne l'accompagne.
  • Les assemblages ont un rôle dans la sécurité. En effet, comme nous l'avons vu avec les domaines d'application, un assemblage est la plus petite unité à laquelle sont données des permissions, ce qui détermine donc si le code qu'il contient peut être exécuté.
  • Les assemblages englobent des types de données. Chaque type de données connait le nom de l'assembly dans lequel il réside, ce qui implique qu'il peut exister deux types différents ayant le même nom dans des assemblages différents.
  • Les assemblages peuvent partager des ressources entre eux. En effet, ils contiennent dans leurs métadonnées des informations permettant de mettre ce partage en oeuvre, comme la liste des types et des ressources accessibles depuis l'extérieur. Un assemblage contient également la liste des assemblages dont il dépend.
  • Les assemblages permettent la gestion des versions. Un assemblage est la plus petite unité à laquelle peut être associée un numéro de version. Ainsi, tous les types, code et ressources qu'il contient sont concernés par le numéro de version. Les dépendances sont aussi affectées par ce numéro. Un assemblage sait donc exactement de quelle version de quel(s) assemblage(s) il dépend.
  • Les assemblages sont une unité de déploiement. Un assemblage contenant son numéro de version ainsi que la description de tous les types de données qui y sont utilisés, cela est logique. De plus, en cas de dépendances, une application n'a pas besoin de les charger pour se lancer, mais peut le faire plus tard, sur demande. Cela permet de garder les applications à une taille raisonnable.

Pour finir ajoutons que les assemblages peuvent être statiques ou dynamiques. Les statiques sont stockées sur disque dans les exécutables portables, c'est à dire les exécutables en Intermediate Language. Il est également possible de créer des assemblages dynamiques, qui sont exécutés directement en mémoire, et peuvent être sauvés sur disque. La création des assemblages peut se faire grâce aux outils fournis avec le framework sdk, mais également par programmation, via la classe Reflection, qui permet de créer des assemblages dynamiques.


Programmer avec les assemblages

Les classes et méthodes permettant de programmer avec les assemblages font partie du namespace System.Reflection. Le terme de "réflection" désigne la capacité à pouvoir analyser un assemblage, c'est à dire du code .NET. Il est possible de charger un assemblage, et d'accéder à partir de là à toutes ses composantes, modules, types et membres. Par type, nous entendons ici tous les types pouvant être définis, c'est à dire les classes, structures, énumérations, etc.
Le mécanisme de réflection permet également d'instancier des classes contenues dans l'assemblage chargé, et d'exécuter leurs méthodes.
Nous allons tout d'abord apprendre à parcourir le contenu d'un assemblage, puis nous verrons comment instancier une classe et appeler une de ses méthodes à partir d'un assembly chargé en mémoire.


Explorer un assemblage

Comme nous l'avons vu en introduction, il est possible de charger un assemblage (en d'autres termes, une dll ou un exe) en mémoire afin de visualiser son contenu. Les exemples de code qui seront donnés dans cette partie sont illustrés dans l'application AsmExplorer, que vous trouverez en téléchargement à la fin de ce cours.
Commençons par charger un assemblage en mémoire.


Charger un assemblage

Les méthodes permettant de charger un assemblage se trouvent dans la classe Assembly du namespace System.Reflection. L'assemblage sera chargé dans le domaine à partir duquel la méthode de chargement est appelée.

La première méthode de chargement, Load, a été abondamment surchargée afin de supporter les différents formats sous lesquels ont peut trouver un assemblage.
La première version charge l'assemblage qui correspond à l'AssemblyName passé en paramètre. Une autre version permet de spécifier en plus un objet Evidence afin d'ajouter des preuves à celle contenues dans l'assemblage.

Assembly Load( AssemblyName assemblage )
Assembly Load( AssemblyName assemblage, Evidence preuve)

Un objet AssemblyName contient toutes sortes d'informations relatives à un assemblage: nom, point d'entrée, répertoire, etc...

AssemblyName nom = new AssemblyName;
nom.Name = "monAssembly";
Assembly monAssemblage = Assembly.Load( nom );

Une autre version de la méthode Load permet de passer, sous forme de tableau d'octets, un assemblage au format COFF. (Common Objet File Format) Il est possible de créer des assemblages en mémoire, auquel cas on peut les récupérer sous cette forme. On peut, en option, passer un autre tableau d'octets contenant les symboles de l'assemblage, et éventuellement un objet Evidence afin d'ajouter des preuves à celle contenues dans l'assemblage.

Assembly Load( byte[] assemblage)
Assembly Load( byte[] assemblage, byte[] symboles );
Assembly Load( byte[] assemblage, byte[] symboles, Evidence preuve )

Dans cette version, la méthode Load charge l'assemblage qui porte le display name passé en paramètre. Attention, le nom passé en paramètre n'est pas le nom de fichier, mais le "display name" de l'assemblage. Il est constitué du nom simple de l'assembly, (par exemple "System.Web") du numéro de version, des informations de culture, et de la clé publique si l'assemblage a un nom fort. Par exemple, le display name de System.Web est "System.Web, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a". Une seconde version permet de spécifier en plus un objet Evidence afin d'ajouter des preuves à celle contenues dans l'assemblage.

Assembly Load( string assemblage );
Assembly Load( string assemblage, Evidence preuve )

Toutes les méthodes précédentes peuvent lever une FileNotFoundException si elles ne trouvent pas l'assemblage spécifié. Si elles le trouvent mais que l'assemblage n'est pas valide, une BadImageFormatException sera levée.
L'exception FileLoadException, qui ne concerne que les versions qui prennent un objet Evidence en paramètre, sera levée si vous tentez de charger deux fois le même assemblage avec des preuves différentes.

La méthode LoadFrom charge l'assemblage dont le nom (comprenant ou non le chemin) est passé en paramètre. Le chemin spécifié peut être absolu ou relatif. Dans le second cas, il sera relatif au répertoire courant. Un version surchargée de la méthode permet de spécifier un objet Evidence afin d'ajouter des preuves à celle contenues dans l'assemblage.

Assembly LoadFrom( string assemblage )
Assembly LoadFrom( string assemblage, Evidence preuve )

Assembly monAssemblage = Assembly.LoadFrom( "maDll.dll" );

Evidence preuve = new Evidence();
preuve.AddHost( new Url("http://www.microapp.com") );
Assembly monAutreAssemblage = Assembly.LoadFrom( "monAutreDll.dll", preuve );

Une FileNotFoundException sera levée si l'assemblage n'est pas trouvé. S'il n'est pas valide, cela provoquera une BadImageFormatException. Enfin, une FileLoadException sanctionnera le fait que nous tentions de charger le même assemblage avec deux preuves différentes.

La dernière méthode de chargement, AssemblyLoadWithPartialName, permet de charger un assemblage à partir d'un nom incomplet. Elle cherchera dans le répertoire de l'application et dans le "global assembly cache". Elle prend une chaine de caractère qui contient ce nom partiel en argument. Il est également possible de passer en plus un objet Evidence, pour spécifier des preuves en sus de celles contenues dans l'assemblage.

Assembly LoadWithPartialName ( string assemblage )
Assembly LoadWithPartialName ( string assemblage, Evidence preuve )

Assembly monAssemblage = Assembly.LoadWithPartialName( "unnomincomplet" );

Evidence preuve = new Evidence();
preuve.AddHost( new Url("http://www.microapp.com") );
Assembly monAutreAssemblage = Assembly.LoadWithPartialName( "unautrenomincomplet", preuve );

Si la méthode ne trouve aucun assemblage correspondant au nom, elle lèvera une FileNotFoundException. Une FileLoadException sera levée si vous tentez de charger deux fois le même assemblage avec des preuves différentes.


Obtenir des informations sur un assemblage

De nombreuses informations sur un assemblage peuvent être obtenues via sa méthode GetName. Elle retourne un objet AssemblyName. Avant de détailler son contenu, voyons les deux versions de la méthode GetName.

AssemblyName GetName()
AssemblyName GetName( bool copiedName )

Le booléen de la deuxième version sert à indiquer si la propriété CodeBase de l'assemblage sera ou non changée pour refléter le fait que l'assemblage est, lors du chargement, copié dans un répertoire temporaire. Vous l'aurez compris, la propriété CodeBase indique l'endroit où est stocké l'assembly.
Examinons maintenant une à une les propriétés de la classe AssemblyName.
  • string CodeBase: cette propriété indique l'endroit où se trouve l'assemblage, à la façon d'une url. Donc, si l'assembly est disponible localement, l'url commencera par "file://".
  • CultureInfo CultureInfo: cette propriété est un objet de type CultureInfo, qui donne des indications sur la culture pour laquelle est définie l'assemblage, comme le symbole monétaire, la façon de le placer, etc...
  • string EscapedCodeBase: cette propriété indique la même chose que CodeBase, sous la forme d'une uri. Elle comprend les caractères d'échappement. ("file://" deviendra par exemple "file:////" car le caractère "/" est échappé en "//")
  • AssemblyNameFlags Flags: cette propriété définit les attributs de l'assembly. AssemblyNameFlags est une énumération qui peut prendre deux valeurs: None pour ne spécifier aucun drapeau, ou PublicKey quand on veut créer un assemblage à nom fort.
  • string FullName: contient le nom complet d'un assemblage. Cette propriété est aussi connue sous le nom de "display name". Comme nous l'avons expliqué plus haut, il est constitué du nom simple de l'assembly, ( par exemple "System.Web") du numéro de version, des informations de culture, et de la clé publique si l'assemblage a un nom fort. Par exemple, le display name de System.Web est "System.Web, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
  • AssemblyHashAlgorithm HashAlgorithm: spécifie l'algortihme de hashage utilisé. AssemblyHashAlgorithm est une énumération qui peut prendre les valeurs MD5, SHA1 ou None.
  • StringNameKeyPair KeyPair: spécifie la paire de clés cryptographiques publiques et privées générées par le créateur de l'assemblage. La classe StringNameKeyPair possède notamment une propriété PublicKey qui contient la clé publique.
  • string Name: contient le nom simple et non crypté de l'assemblage. (exemple: "System.Web")
  • Version Version: spécifie les numéros de version majeur, mineur, la révision et le numéro de build de l'assemblage. L'objet Version contient une propriété pour chacun de ces numéros.
  • AssemblyVersionCompatibility VersionCompatibility: indique le degré de compatibilité de cet assembly avec les autres. C'est une énumération qui peut prendre trois valeurs: SameDomain si l'assemblage ne peut pas s'exécuter si d'autre versions s'exécutent déjà dans le même domaine d'application, SameMachine si l'assemblage ne peut s'exécuter si d'autres versions s'exécutent sur la même machine, et SameProcess si l'assemblage ne peut s'exécuter si d'autres versions s'exécutent dans le même processus. Actuellement cette propriété n'est pas utilisée lors du chargement et est considérée comme étant toujours à SameMachine.

Voilà les informations que nous pouvons récupérer dans un objet AssemblyName. Voyons maintenant comment nous pouvons visualiser le contenu d'un assemblage.


Visualiser le contenu d'un assemblage

Un assemblage contient des modules, qui eux-même contiennent des types, qui contiennent des membres. Le réflection fournit des objets qui encapsulent les assemblages, les modules et les types.
Il est donc possible, pour un assemblage donné, de connaitre les modules qu'il contient, les types qui en font partie, et leurs constructeurs, méthodes, propriétés et évènements. Nous pouvons également obtenir des informations détaillées sur la paramètres des constructeurs et des méthodes.
Nous allons démarrer avec les modules, puis, pas à pas, obtenir toutes les informations possibles. Les méthodes présentées ici ne sont pas le seul moyen d'obtenir des informations. Par exemple, nous récupérons les types à partir des modules, ce que nous pourrions directement faire à partir de l'assemblage. Considérez donc ceci comme une des nombreuses manières de procéder.

L'obtention des modules passe par la méthode GetModules, qui renvoie un tableau d'objets Module.

Assembly monAssembly = Assembly.GetExecutingAssembly();
Module[] lesModules = monAssembly.GetModules();

Nous avons dans cet exemple utilisé la méthode GetExecutingAssembly, qui retourne l'assemblage dans lequel tourne le code courant. Le code appelle ensuite la méthode GetModules. Le premier Module du tableau contient le manifeste de l'assembly.

Un module est un exécutable portable de type .dll ou .exe. Un assemblage est en fait un assemblage, justement, de ces modules.
A partir de l'objet Module, nous pouvons récupérer, entre autres, les méthodes globales du module ainsi que ses types.
La récupération des méthodes globales se fait par le biais de GetMethods, qui retourne un tableau d'objets de type MethodInfo.

MethodInfo[] mInfo = mesModules[1].GetMethods();

Un objet MethodInfo contient, comme son nom l'indique, toutes les informations relatives à une méthode. Elle compte énormément de propriétés, dont voici les principales.
  • Type DeclaringType: contient la classe dans laquelle a été déclaré ce membre. Si ce membre est hérité d'une autre classe, c'est cette autre classe qui figurera dans cette propriété.
  • bool IsAbstract: indique si la méthode est abstraite
  • bool IsConstructor: indique si la méthode est un constructeur
  • bool IsFinal: indique si la classe est finale
  • bool IsPublic: indique si la méthode est publique
  • bool IsPrivate: indique si la méthode est privée
  • bool IsStatic: indique si la méthode est statique
  • bool IsVirtual: indique si la méthode est virtuelle
  • string Name: contient le nom de la méthode
  • Type ReturnType: contient le type de données que retourne la méthode

De même, voyons les méthodes principales de l'objet Method:
  • MethodInfo GetBaseDefinition(): cette méthode est utilisée avec méthodes surchargées par héritage, pour obtenir des informations relatives à la définition initiale de la méthode.
  • ParameterInfo[] GetParameters(): cette méthode permet d'obtenir toutes les informations relatives aux paramètres qu'accepte le méthode. Nous détaillerons l'objet ParameterInfo dans quelques lignes.
  • Invoke: nous passerons volontairement sur cette méthode, qui sera expliquée dans la partie suivante. Sachez tout de même qu'elle a pour but d'invoquer la méthode référencée par l'objet Method.

Nous pouvons obtenir des informations sur les paramètres de la méthode, qui seront stockées dans un tableau d'objets ParameterInfo. Chacun de ces objets concerne un paramètre en particulier. Examinons les propriétés principales de ParameterInfo:
  • object DefaultValue: contient la valeur par défaut du paramètre, s'il en a une.
  • bool IsIn: indique si le paramètre est précédé de la directive in.
  • bool IsOptional: indique si le paramètre est optionnel.
  • bool IsOut: indique si le paramètre est précédé de la directive out.
  • MemberInfo Member: spécifie un objet MemberInfo qui contient des informations sur le membre dans lequel ce paramètre est implémenté. (MemberInfo est la classe abstraite dont dérivent toutes les classes d'information, comme EventInfo, PropertyInfo ou MethodInfo)
  • string Name: contient le nom du paramètre.
  • Type ParameterType: indique de quel type est le paramètre
  • int Position: indique la position que le paramètre occupe dans la signature de la méthode

Nous savons maintenant récupérer les informations relatives aux méthodes globales d'un module. Voyons à present comment récupérer les types qu'il contient.
C'est la méthode GetTypes qui réalise cette opération. Elle renvoie un tableau d'objets Type.
Type lesTypes[] = mesModules[1].GetTypes();

Une ReflectionTypeLoadException sera levée si une ou plusieurs classes dans le module ne peuvent être chargées.

L'objet Type est à la base des opérations de réflection. C'est à travers lui que sont représentés les types de données sur le système.
A partir de cet objet, on pourra donc obtenir une foule d'informations: les méthodes du type, ses constructeurs, ses propriétés et ses évènements.
L'obtention de la liste des méthodes se fait via GetMethods, dont voici les deux prototypes:

Method[] GetMethods()
Method[] GetMethods( BindingFlags drapeaux )

La première version retourne l'intégralité des méthodes faisant partie du type, tandis que la deuxième permet de spécifier une combinaison de drapeaux indiquant quel type de méthodes on veut récupérer. Par exemple,

MethodInfo[] mesMethodes = monType.GetMethods(BindingFlags.Public | 
                                   BindingFlags.Instance); 

ne récupèrera que les méthodes publiques.
Voici les différents drapeaux qui sont à notre disposition pour cette méthode: (il existe d'autres drapeaux, mais ils n'ont aucune utilité avec GetMethods)
  • BindingFlags.DeclaredOnly: indique que seules les méthodes déclarées à ce niveau seront retournées. En d'autres termes, les méthodes héritées ne seront pas sélectionnées.
  • BindingFlags.Default: indique qu'aucun drapeau n'est spécifié.
  • BindingFlags.FlattenHierarchy: indique que les méthodes statiques définies plus haut dans la hiérarchie des classes seront retournées.
  • BindingFlags.Instance: indique que les méthodes d'instance seront retournées.
  • BindingFlags.NonPublic: indique que les méthodes non publiques seront retournées.
  • BindingFlags.Public: indique que les méthodes publiques seront retournées.
  • BindingFlags.Static: indique que les méthodes statiques seront retournées.

Vous devez spécifier BindingsFlags.Instance ou BindingFlags.Static si vous utilisez BindingFlags.Public ou BindingFlag.NonPublic, sans quoi aucune méthode ne sera retournée.

La méthode GetConstructors permet de ne récupérer que les constructeurs du type. Voici ses deux prototypes:
ConstructorInfo[] GetConstructors ()
ConstructorInfo[] GetConstructors ( BindingFlags drapeaux )

Les drapeaux que l'on peut spécifier dans la deuxième version sont les mêmes que ceux de la méthodes GetMethods.

ConstructorInfo[] mesConstructeurs = monType.GetConstructors();

La méthode retourne un tableau d'objetsde type ConstructorInfo. Comme vous vous en doutez, cette classe est très semblable à la classe MethodInfo, c'est pourquoi nous ne la détaillerons pas.

Les propriétés du type peuvent être obtenues grâce à la méthode GetProperties:

PropertyInfo[] GetProperties()
PropertyInfo[] GetProperties( BindingFlags drapeaux )

Les drapeaux utilisés par cette méthode sont les mêmes que ceux utilisés par GetMethods.
L'objet PropertyInfo:
Propriétés principales:
  • string Name: indique le nom de la propriété
  • Type PropertyType: indique le type de la propriété

Les évènements gérés par le type peuvent s'obtenir par le biais de la méthode GetEvents.
EventInfo[] GetEvents()
EventInfo[] GetEvents( BindingFlags drapeaux )

Les drapeaux utilisés par cette méthode sont les mêmes que ceux utilisés par GetMethods. Elle retourne un tableau d'objets EventInfo, donc voici les propriétés principales:
  • string Name: contient le nom de l'évènement
  • bool IsMulticast: indique si le delegate associé à l'évènement est associé à plusieurs méthodes.
La méthode principale de l'objet EventInfo est la méthode GetRaiseMethod, qui retourne un objet MethodInfo correspondant à la méthode qui sera appelée si cet évènement se produit. Elle n'accepte aucun paramètre.

Enfin, il est possible de récupérer toutes les interfaces implémentées dans un type. Cela se fait via la méthode GetInterfaces. Elle ne prend rien en paramètre, et retourne une tableau d'objets Type représentant toutes les interfaces impémentées ou héritées par le type à partir duquel la méthode est appelée.

Maintenant que nous savons récupérer tout ce qui compose un type, intéressons-nous aux propriétés principales de la classe Type, qui sont pleines d'informations utiles à propos du type.
  • Assembly Assembly: indique l'assemblage qui contient ce type
  • string AssemblyQualifiedName: indique le nom qualifié du type, ceci incluant le nom de l'assemblage à partir duquel le type a été chargé.
  • Type BaseType: indique le type duquel le type courant hérite. (c'est à dire la classe juste au-dessus dans la hiérarchie) Si la classe mère est object, cette propriété sera à null.
  • Type DeclaringType: retourne l'objet Type qui correspond à la classe dans laquelle est défini le type courant.
  • string FullName: le nom complet du type, ceci incluant le namespace dans lequel il a été déclaré.
  • bool HasElementType: indique si le type courant renferme un autre type ou s'y réfère, en d'autre termes, si le type est un tableau, un pointeur, ou bien est passé par référence. Par exemple, Type.GetType("Int32[]").HasElementType retournera true, mais Type.GetType("Int32").HasElementType retournera false.
  • bool IsAbstract: indique si le type est abstrait
  • bool IsArray: indique si le type est un tableau
  • bool IsByRef: indique si le type est passé par référence
  • bool IsClass: indique si le type est une classe
  • bool IsCOMObject: indique si le type est un objet COM
  • bool IsEnum: indique si le type est une énumération
  • bool IsImport: indique si le type est importé d'une autre classe
  • bool IsInterface: indique si le type est une interface
  • bool IsNotPublic: indique si le type n'est pas public
  • bool IsPointer: indique si le type est un pointeur
  • bool IsPrimitive: indique si le type est un des types de base
  • bool IsPublic: indique si le type est public
  • bool IsSealed: indique si le type est déclaré comme sealed
  • bool IsSerializable: indique si le type est sérialisable
  • bool IsValue: indique si le type est une "type de valeur", c'est à dire ni une classe ni une interface
  • MemberTypes MemberType: indique le type du membre. MemberTypes est une énumération qui peut prendre les valeurs All (tous les types), Constructor (constructeur), Custom (type utilisateur), Event (évènement), Field (champ), Method (méthode), NestedType (type imbriqué), Property (propriété) et TypeInfo (type).
  • Module Module: indique le module dans lequel est défini le type
  • string namespace: indique le namespace dans lequel le type est contenu
  • Type ReflectedType: indique quelle classe a été utilisée pour obtenir ce type
  • ConstructorInfo TypeInitializer: renvoie un ConstructorInfo qui contient le nom du constructeur du type courant.


Instancier un objet d'un assemblage

Comme pour la visualisation du contenu d'un assemblage, il existe de nombreuses façons d'en instancier un objet et d'en appeler une méthode. Nous verrons ici plusieurs de ces méthodes.
Nous utiliserons dans les exemples la classe Client, définie dans le namespace "AsmExplorer", c'est à dire le namespace du programme que vous trouverez en téléchargement à la fin de ce cours.
Voici le contenu de la classe Client:

public class Client
{
  private string nom = "Pantélimon";
  private string prenom = "Mathieu";

  public Client( string nom, string prenom )
  {
    this.nom = nom;
    this.prenom = prenom;
    MessageBox.Show( "Constructeur avec arguments" );
  }

  public Client()
  {
    MessageBox.Show( "Constructeur par défaut" );
  }

  public void affiche()
  {
    MessageBox.Show( "Nom: " + nom + "\nPrénom: " + prenom );
  }
}

Cette classe contient deux chaines de caractères, initialisées avec une valeur par défaut. Elle possède un constructeur par défaut, c'est à dire sans paramètres, et un constructeur qui accepte deux chaines de caractères, afin de pouvoir changer le contenu des string contenues dans l'objet. Une méthode affiche vient clore le tout. Elle affiche les deux chaines de caractères contenues dans la classe.
Voyons comment instancier cette classe et appeler une de ses méthodes.

Type t = Type.GetType( "AsmExplorer.Client" );

object obj1;
obj1=Activator.CreateInstance(t);
obj1.GetType().InvokeMember("affiche",BindingFlags.Default 
        |BindingFlags.InvokeMethod,null,obj1,new object[]{});

Tout d'abord, nous récupérons un objet Type associée à la classe que nous voulons instancier, grâce à la ligne:

Type t = Type.GetType( "AsmExplorer.Client" );

Vous noterez qu'il faut lui fournir le nom complet du type, namespace compris. Une fois ce type acquis, il nous faut instancier la classe:

object obj1;
obj1=Activator.CreateInstance(t);

La méthode CreateInstance de l'objet Activator instancie la classe correspondant au type passé en paramètre, et renvoie l'instance sous forme d'object. Elle utilise, si elle en trouve un qui lui convient, le constructeur le plus approprié suivant les paramètres qu'on lui fournit. Elle lèvera une MissingMethodException si sa recherche est infructueuse. Ici, nous n'en avons spécifié aucun, ce sera donc le constructeur par défaut qui sera choisi. Pour indiquer à CreateInstance qu'il faut passer des paramètres au constructeur, nous devons lui fournir un tableau d'object qui contiendra lesdits paramètres.
Si nous définissons notre tableau d'object ainsi,

object[] args = new object[2];
args[0] = "Boussin";
args[1] = "Laetitia";

voilà ce que devient l'instanciation de la classe:

object obj1;
obj1=Activator.CreateInstance(t, args);

Une fois la classe instanciée, nous pouvons appeler ses méthodes. Nous allons ici invoquer la méthode affiche, grâce à la méthode InvokeMember de l'objet Type:

obj1.GetType().InvokeMember("affiche",BindingFlags.Default 
        |BindingFlags.InvokeMethod,null,obj1,new object[]{});

La méthode InvokeMember prend les paramètres suivants:
  • une chaine de caractères qui représente la méthode à appeler
  • un ou plusieurs drapeaux de l'énumération BindingFlags. (on passe en général BindingFlags.Default et BindingFlags.InvokeMethod)
  • le binder à utiliser. (le binder est l'objet qui assure la sélection de la bonne méthode en cas de surcharge, par exemple) On passe en général null pour utiliser le binder par défaut.
  • l'objet à partir duquel la méthode est invoquée
  • un tableau d'object qui définit les paramètres à passer à la méthode

Nous aurions aussi pu passer par un objet MethodInfo pour invoquer la méthode:
object obj1;
obj1=Activator.CreateInstance( Type.GetType("AsmExplorer.Client") );
MethodInfo m = Type.GetType("AsmExplorer.Client").GetMethod("affiche");
m.Invoke( obj1, new object[]{} );

Ici nous avons utilisé la méthode Invoke. Elle prend en premier paramètre l'objet à partir duquel l'appel de la méthode sera fait, et en second paramètre un tableau d'objets contenant les éventuels paramètres à passerà la méthode appelée.

Pour appeler une méthode statique, il est inutile d'instancier la classe qui la contient, et nous devons dans ce cas passer null à InvokeMember. Admettons ici que la variable asm soit une Assembly dans laquelle nous ayons chargé System.Windows.Forms. Ce code appelle la méthode Show de ma classe MessageBox:

Type t1 = asm.GetType( "System.Windows.Forms.MessageBox" );
t1.InvokeMember( "Show",BindingFlags.Default BindingFlags.InvokeMethod,null,
null,new object[1]{"Invocation de méthode statique"});


Annexe

Vous pouvez télécharger le programme AsmExplorer dont il est question dans ce cours en cliquant ici (http://www.mangue.org/cours/csharp/download/AsmExplorer.zip).

Outils personels