1. Introduction
La réflexion (du anglais Reflection) — c'est la capacité de votre programme à explorer sa propre structure pendant l'exécution. Imaginez que votre programme puisse se demander : « Quels sont mes méthodes ? Quels sont les propriétés de ce type ? » et obtenir la réponse à runtime.
Exemples d'utilisation de la réflexion dans la vraie vie
- Création de plugins et modules : Vous n'avez pas besoin de savoir à l'avance avec quelle classe vous allez travailler — il suffit de charger une DLL et d'inspecter ses méthodes à la volée.
- Automatisation des tests : Les frameworks de test, par exemple xUnit et NUnit, trouvent les méthodes de test via la réflexion.
- Sérialisation : Avec la réflexion, on peut automatiquement transformer n'importe quel objet en JSON/XML sans parcourir manuellement les propriétés.
- Validation des données via les attributs : Par exemple, vous avez ajouté l'attribut [Required] à une propriété — et vous voulez vérifier automatiquement toutes ces propriétés.
Pourquoi le connaître pour les entretiens
La maîtrise de la réflexion est souvent demandée en entretien, car elle démontre votre compréhension du fonctionnement de .NET et votre capacité à résoudre des problèmes dynamiques. Si vous voulez écrire vos propres frameworks ou extensions, la réflexion est indispensable.
2. Espace de noms et classes de base de la réflexion
Tout le fonctionnel de la réflexion en C# est accessible via l'espace de noms System.Reflection. N'oubliez pas au début du fichier :
using System.Reflection;
Types principaux de la réflexion
| Classe/Type | Description |
|---|---|
|
Représente la description d'un type dans .NET (par exemple, classe, interface, enum, etc.) |
|
Propriété d'un type |
|
Méthode d'un type |
|
Champ d'un type |
|
Constructeur d'un type |
|
Représente une assembly (EXE ou DLL) |
Méthodes principales de la classe Type
| Méthode | Description |
|---|---|
|
Retourne toutes les propriétés publiques |
|
Retourne tous les champs publics |
|
Retourne toutes les méthodes publiques |
|
Retourne tous les constructeurs publics |
|
Retourne toutes les interfaces implémentées par le type |
|
Retourne tous les attributs appliqués au type |
On peut souvent passer des BindingFlags à ces méthodes pour accéder aux membres privés ou statiques, mais pour l'instant on se limite au scénario de base.
3. Tout premier pas : obtenir un objet Type
Toutes les techniques de réflexion reposent sur l'objet Type.
Il existe plusieurs façons d'obtenir cet objet, voyons chacune :
Méthode 1 : via un objet
string text = "Hello, Reflection!";
Type type1 = text.GetType();
Console.WriteLine(type1.Name); // String
Méthode 2 : via le nom du type
Type type2 = typeof(int);
Console.WriteLine(type2.FullName); // System.Int32
Méthode 3 : par chaîne (dynamiquement)
Type type3 = Type.GetType("System.Double");
Console.WriteLine(type3); // System.Double
Attention ! Pour les classes utilisateur situées dans d'autres assemblies, il faut préciser le nom complet du type avec l'assembly.
4. Explorer la structure du type : propriétés, méthodes, champs et constructeurs
Le moment de vérité est arrivé : vous avez obtenu un objet Type — et maintenant vous pouvez apprendre tout ce que vous voulez.
Obtenir la liste des propriétés
Type type = typeof(DateTime);
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine($"{prop.PropertyType.Name} {prop.Name}");
}
Sortie :
Int32 Day
Int32 Month
Int32 Year
DayOfWeek DayOfWeek
...
Obtenir la liste des méthodes
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
Console.WriteLine($"{method.ReturnType.Name} {method.Name}()");
}
Les types volumineux peuvent contenir énormément de méthodes ! Souvent vous n'avez pas besoin de toutes, mais seulement des "vos propres" (par exemple, en excluant les méthodes héritées de object). On verra ça plus tard.
Obtenir la liste des champs
FieldInfo[] fields = type.GetFields();
foreach (var field in fields)
{
Console.WriteLine($"{field.FieldType.Name} {field.Name}");
}
Mais la plupart des classes ordinaires n'ont pas de champs publics, puisque l'encapsulation est importante :)
Obtenir les constructeurs
ConstructorInfo[] constructors = type.GetConstructors();
foreach (var ctor in constructors)
{
Console.WriteLine($"Constructeur : {ctor}");
}
5. Appliquer la réflexion en pratique : analyser notre application
Rappel : dans notre projet pédagogique il y a une classe TaskItem où nous stockons les tâches. Explorons-la via la réflexion.
public class TaskItem
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DueDate { get; set; }
public bool IsCompleted;
}
Voici comment obtenir toute la "structure interne" de notre type à runtime :
Type taskType = typeof(TaskItem);
Console.WriteLine("Propriétés :");
foreach (var prop in taskType.GetProperties())
Console.WriteLine($" {prop.PropertyType.Name} {prop.Name}");
Console.WriteLine("Champs :");
foreach (var field in taskType.GetFields())
Console.WriteLine($" {field.FieldType.Name} {field.Name}");
Console.WriteLine("Méthodes :");
foreach (var method in taskType.GetMethods())
Console.WriteLine($" {method.ReturnType.Name} {method.Name}()");
Essayez d'ajouter de nouvelles propriétés à la classe et de voir comment la réflexion les détecte. C'est ainsi que fonctionnent les sérialiseurs universels et les inspecteurs d'objets.
6. Visualisation : comment est structuré un objet Type (schéma)
+-------------------------+
| Type |
+-------------------------+
| .Name |
| .FullName |
| .Namespace |
| .Assembly |
+-------------------------+
| .GetProperties() |
| .GetFields() |
| .GetMethods() |
| .GetConstructors() |
| .GetInterfaces() |
| .GetCustomAttributes() |
+-------------------------+
Voici le "portrait" du type — il a un nom, une assembly, un ensemble de membres et des attributs.
Obtenir les informations de base sur un type
L'objet Type peut dire quel type c'est :
Type t = typeof(double);
Console.WriteLine(t.Name); // Double
Console.WriteLine(t.FullName); // System.Double
Console.WriteLine(t.Namespace); // System
Console.WriteLine(t.IsClass); // False
Console.WriteLine(t.IsValueType); // True
Console.WriteLine(t.IsEnum); // False
Console.WriteLine(t.IsPrimitive); // True
7. Rechercher une propriété, méthode ou champ par nom
Propriété
var prop = taskType.GetProperty("Title");
if (prop != null)
{
Console.WriteLine($"Type de la propriété 'Title' : {prop.PropertyType}");
}
Méthode
var method = taskType.GetMethod("ToString");
if (method != null)
{
Console.WriteLine($"La méthode 'ToString' retourne : {method.ReturnType}");
}
Champ
var field = taskType.GetField("IsCompleted");
if (field != null)
{
Console.WriteLine($"Type du champ 'IsCompleted' : {field.FieldType}");
}
8. Utiliser la réflexion pour un accès dynamique aux valeurs
Maintenant on ne se contente pas de regarder, on peut aussi toucher les valeurs !
Lire la valeur d'une propriété
TaskItem item = new TaskItem { Id = 1, Title = "Tester la réflexion", DueDate = DateTime.Today };
PropertyInfo titleProp = item.GetType().GetProperty("Title");
if (titleProp != null)
{
object value = titleProp.GetValue(item);
Console.WriteLine($"Titre : {value}");
}
Définir la valeur d'une propriété
titleProp.SetValue(item, "Titre modifié");
Console.WriteLine(item.Title);
Travail avec les champs
FieldInfo completedField = item.GetType().GetField("IsCompleted");
completedField.SetValue(item, true);
Console.WriteLine(item.IsCompleted); // true
Pour les champs et propriétés privés, il faut des flags spéciaux, par exemple BindingFlags.NonPublic. On en parlera plus tard.
9. Appeler des méthodes via la réflexion
On peut appeler des méthodes sans les connaître à la compilation !
TaskItem task = new TaskItem { Title = "La réflexion, c'est génial !" };
MethodInfo method = task.GetType().GetMethod("ToString");
if (method != null)
{
object result = method.Invoke(task, null);
Console.WriteLine(result);
}
Si la méthode a des paramètres, il faut les passer comme un tableau d'objets :
public class Calculator
{
public int Add(int a, int b) => a + b;
}
var calc = new Calculator();
var addMethod = typeof(Calculator).GetMethod("Add");
object[] parameters = { 5, 3 };
object sumResult = addMethod.Invoke(calc, parameters);
Console.WriteLine(sumResult); // 8
10. Création dynamique d'objets via le constructeur
La réflexion permet de créer des objets sans utiliser l'opérateur new !
Type taskType = typeof(TaskItem);
object newTask = Activator.CreateInstance(taskType);
// C'est le même TaskItem (mais le type est object)
Console.WriteLine(newTask.GetType().Name); // TaskItem
On peut passer des paramètres au constructeur :
public class User
{
public string Name { get; }
public User(string name) { Name = name; }
}
Type userType = typeof(User);
object user = Activator.CreateInstance(userType, "Élise");
Console.WriteLine(((User)user).Name); // Élise
11. Erreurs typiques en travaillant avec System.Reflection
Erreur n°1 : Accéder aux membres privés sans BindingFlags.
Par défaut la réflexion ne voit que les membres publics. Sans BindingFlags.NonPublic, l'accès aux champs ou méthodes privés est impossible.
Erreur n°2 : Ignorer la vérification de null.
Les méthodes GetProperty, GetMethod et autres peuvent retourner null si le membre n'est pas trouvé. Sans vérification, cela mène à une NullReferenceException.
Erreur n°3 : Mauvais choix de la surcharge d'une méthode.
Si plusieurs surcharges existent, GetMethod peut retourner la mauvaise variante. Utilisez GetMethod en précisant les types des paramètres.
Erreur n°4 : Utiliser la réflexion dans du code critique pour la performance.
La réflexion est plus lente que les appels directs. Pour du code à haute performance, utilisez le caching des métadonnées ou des delegates.
GO TO FULL VERSION