Chargement...

Réflexion et liaison tardive en C#

Apprenez la réflexion et la liaison tardive en C# pour analyser les types à l’exécution avec des exemples.

En C#, la réflexion (Reflection) permet d’accéder aux informations de type à l’exécution (runtime), de manipuler les types et de créer des objets dynamiquement. Le Late Binding (liaison tardive) consiste à accéder à des objets dont le type est inconnu au moment de la compilation. Grâce à ces deux mécanismes, il est possible de concevoir des systèmes flexibles, modulaires ou basés sur des plugins.


Qu’est-ce que la Réflexion ?

La réflexion est un puissant mécanisme qui permet d’inspecter et d’utiliser, à l’exécution, les types (classes, méthodes, propriétés, champs, etc.) contenus dans un assembly. Par exemple, il est possible d’afficher les classes, les méthodes, les attributs ou leurs valeurs dans un fichier d’assembly.


using System;
using System.Reflection;

class Exemple
{
    public int Nombre { get; set; }
    public void Ecrire() => Console.WriteLine("Méthode Ecrire() appelée.");
}

class Programme
{
    static void Main()
    {
        Type type = typeof(Exemple);
        Console.WriteLine($"Nom de la classe : {type.Name}");

        // Lister les propriétés
        foreach (var prop in type.GetProperties())
            Console.WriteLine($"Propriété : {prop.Name}");

        // Lister les méthodes
        foreach (var methode in type.GetMethods())
            Console.WriteLine($"Méthode : {methode.Name}");
    }
}

Dans cet exemple, l’objet Type fournit l’accès à toutes les informations sur la structure de la classe. Des méthodes telles que GetMethods(), GetProperties() et GetFields() permettent d’explorer les détails d’un type.


Qu’est-ce que le Late Binding ?

Le Late Binding (liaison tardive) signifie créer une instance d’une classe à l’exécution, alors que son type est inconnu à la compilation. Grâce à la réflexion, on peut charger la classe, créer un objet et invoquer ses méthodes dynamiquement. Cette technique est très utilisée dans les systèmes de plugins, le chargement dynamique de DLL externes et la gestion de types dynamiques.


using System;
using System.Reflection;

class Salutateur
{
    public void DireBonjour(string nom)
    {
        Console.WriteLine($"Bonjour, {nom} !");
    }
}

class Programme
{
    static void Main()
    {
        // Création d’un objet à l’exécution à partir du type
        Type type = Type.GetType("Salutateur");
        object instance = Activator.CreateInstance(type);

        // Appel d’une méthode (Late Binding)
        MethodInfo methode = type.GetMethod("DireBonjour");
        methode.Invoke(instance, new object[] { "Ahmed" });
    }
}

Ici, la classe Salutateur est chargée dynamiquement à partir de son nom. Comme son type était inconnu à la compilation, on parle de liaison tardive.


Chargement dynamique via Assembly

Avec les méthodes Assembly.LoadFrom() ou Assembly.Load(), il est possible de charger une DLL externe et d’en découvrir les types. Cette approche est souvent utilisée dans les architectures modulaires ou à plugins.


using System;
using System.Reflection;

class Programme
{
    static void Main()
    {
        // Charger une DLL externe
        Assembly dll = Assembly.LoadFrom("MaBibliotheque.dll");

        // Lister les types
        foreach (var type in dll.GetTypes())
            Console.WriteLine($"Type : {type.FullName}");

        // Créer une instance et appeler une méthode
        Type typeCible = dll.GetType("MaBibliotheque.MathHelper");
        object instance = Activator.CreateInstance(typeCible);
        MethodInfo methode = typeCible.GetMethod("Additionner");

        object resultat = methode.Invoke(instance, new object[] { 3, 5 });
        Console.WriteLine($"Résultat de l’addition : {resultat}");
    }
}

Grâce à cette approche, des bibliothèques externes peuvent être chargées à l’exécution et leurs types ou méthodes utilisés dynamiquement.


Accès dynamique aux propriétés et aux champs

La réflexion ne se limite pas à l’appel de méthodes ; elle permet également d’accéder aux valeurs des propriétés et des champs, et même de les modifier.


using System;
using System.Reflection;

class Voiture
{
    public string Marque { get; set; } = "Ford";
    private int Vitesse = 120;
}

class Programme
{
    static void Main()
    {
        var voiture = new Voiture();
        Type type = voiture.GetType();

        // Lire une propriété publique
        var prop = type.GetProperty("Marque");
        Console.WriteLine("Marque : " + prop.GetValue(voiture));

        // Accéder à un champ privé
        var champ = type.GetField("Vitesse", BindingFlags.NonPublic | BindingFlags.Instance);
        Console.WriteLine("Vitesse : " + champ.GetValue(voiture));

        // Modifier la valeur
        champ.SetValue(voiture, 200);
        Console.WriteLine("Nouvelle vitesse : " + champ.GetValue(voiture));
    }
}

Grâce à BindingFlags, on peut contrôler l’accès aux membres publics, privés ou statiques.


Accès aux informations d’attribut

La réflexion permet également d’accéder aux informations des Attributes définis sur les classes ou les membres. Cette fonctionnalité est largement utilisée dans les bibliothèques basées sur les métadonnées (par ex. ORM, sérialiseurs).


using System;

[AttributeUsage(AttributeTargets.Class)]
class InfoAttribute : Attribute
{
    public string Description { get; }
    public InfoAttribute(string description) => Description = description;
}

[Info("Cette classe est donnée à titre d’exemple.")]
class Exemple { }

class Programme
{
    static void Main()
    {
        Type type = typeof(Exemple);
        var attr = (InfoAttribute)Attribute.GetCustomAttribute(type, typeof(InfoAttribute));

        Console.WriteLine($"Description : {attr.Description}");
    }
}

Ce mécanisme permet d’ajouter des métadonnées aux classes et de les lire à l’exécution.


Considérations de performance et de sécurité


Exemple : Système de plugins

La réflexion est souvent utilisée pour construire des systèmes modulaires ou à plugins. L’exemple suivant recherche dans une DLL les classes qui implémentent l’interface IPlugin et les charge dynamiquement.


using System;
using System.Linq;
using System.Reflection;

public interface IPlugin
{
    string Nom { get; }
    void Executer();
}

class Programme
{
    static void Main()
    {
        Assembly dll = Assembly.LoadFrom("PaquetPlugin.dll");
        var typesPlugin = dll.GetTypes().Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface);

        foreach (var type in typesPlugin)
        {
            IPlugin plugin = (IPlugin)Activator.CreateInstance(type);
            Console.WriteLine($"Plugin : {plugin.Nom}");
            plugin.Executer();
        }
    }
}

Grâce à cette structure, de nouveaux plugins peuvent être intégrés à l’exécution, même s’ils étaient inconnus lors de la compilation.


TL;DR

  • Reflection : fournit des informations de type et un accès dynamique à l’exécution (Type, MethodInfo, PropertyInfo, etc.).
  • Late Binding : permet de charger et d’utiliser des classes à l’exécution, même si elles sont inconnues à la compilation.
  • Les DLL externes peuvent être chargées dynamiquement avec Assembly.LoadFrom().
  • Utilisez BindingFlags pour accéder aux membres privés.
  • Coût élevé en performance — à utiliser uniquement lorsque nécessaire.
  • Couramment utilisé dans les plugins, sérialiseurs, ORM et frameworks de test.

Articles connexes