La plateforme .NET
Ce que ce chapitre apporte
- Décrire la chaîne qui va du code source à l'exécution d'un programme .NET.
- Expliquer ce qu'est un assemblage et ce qu'il contient.
- Définir le CLR et l'exécution managée.
- Distinguer .NET Framework, .NET Core et les versions unifiées.
- Situer le rôle du ramasse-miettes dans l'exécution managée.
- Expliquer les deux modes de déploiement et leurs conséquences.
Du code source à l'exécution
La compilation d'un programme C# ne produit pas de binaire natif. Elle produit du code intermédiaire, indépendant du processeur et du système.
fichier.cs
| compilateur C# (Roslyn)
v
code intermediaire (CIL) + metadonnees
| regroupes dans un assemblage : .dll ou .exe
v
moteur d'execution (CLR)
| compilation a la volee, methode par methode
v
instructions machine, executees
Ce détour a un coût au démarrage et deux avantages. Le même assemblage s'exécute sur des architectures différentes, puisque la traduction finale a lieu sur la machine cible. Et le moteur d'exécution dispose d'informations complètes sur les types manipulés, ce qui lui permet d'assurer des vérifications que du code natif ne permet pas.
Le CIL (Common Intermediate Language) est le jeu d'instructions produit par le compilateur, commun à tous les langages de la plateforme.
Le compilateur à la volée (JIT, Just In Time) traduit le CIL en instructions machine au moment où une méthode est appelée pour la première fois. Le résultat est conservé pour les appels suivants.
Deux mécanismes atténuent ce coût : la compilation anticipée, qui traduit à l'avance tout ou partie du code, et la compilation par paliers, qui produit d'abord une version rapide à générer puis la remplace par une version optimisée si la méthode est souvent appelée.
L'assemblage
Un assemblage est l'unité de déploiement et de versionnement de la plateforme. Il prend la forme d'un fichier .dll ou .exe et contient le code intermédiaire, les métadonnées décrivant les types, et un manifeste.
Le manifeste est ce qui distingue un assemblage d'un simple fichier de code compilé. Il porte le nom de l'assemblage, sa version, sa culture, éventuellement une signature, et la liste des assemblages dont il dépend avec les versions attendues.
Cette information permet au moteur d'exécution de résoudre les dépendances au chargement, et de refuser de démarrer plutôt que de planter plus tard sur un type introuvable.
Elle produisait aussi une difficulté connue sous le nom d'enfer des DLL : deux applications ayant besoin de versions différentes de la même bibliothèque entraient en conflit.
.NET Core a supprimé le GAC. Chaque application embarque ses dépendances, ce qui coûte de l'espace et supprime la classe de problèmes entière.
L'exécution managée
Le CLR (Common Language Runtime) est le moteur d'exécution de la plateforme. Il charge les assemblages, compile le code intermédiaire, gère la mémoire, applique les contrôles de type et de sécurité, et traite les exceptions.
Un code exécuté sous son contrôle est dit managé. Un code compilé directement en instructions machine, hors de ce contrôle, est dit non managé.
Ce que le moteur prend en charge à la place du programmeur :
| Prise en charge | Ce que cela évite |
|---|---|
| Allocation et libération de la mémoire | fuites, libérations doubles, pointeurs invalides |
| Vérification des types au chargement | conversions incohérentes détectées trop tard |
| Contrôle des accès mémoire | dépassements de tableau silencieux |
| Gestion structurée des exceptions | codes de retour ignorés |
Ces ressources-là restent à la charge du programmeur, à travers l'interface
IDisposable et le bloc using. Le chapitre sur la mémoire y revient en détail.
Framework, Core, et la suite
Trois noms coexistent dans la documentation, et ils ne désignent pas des versions successives d'une même chose.
| .NET Framework | .NET Core | .NET 5 et suivants | |
|---|---|---|---|
| Systèmes | Windows seulement | Windows, Linux, macOS | Windows, Linux, macOS |
| Moteur | CLR | CoreCLR | CoreCLR |
| Distribution | installé sur la machine | embarqué ou installé | embarqué ou installé |
| Code source | fermé | ouvert | ouvert |
| Évolution | maintenance | remplacé par .NET 5 | ligne actuelle |
Le .NET Framework reste installé sur des parcs Windows entiers et continue d'être maintenu, mais il ne reçoit plus de nouveautés. .NET Core l'a réécrit pour être multiplateforme et modulaire. À partir de la version 5, les deux lignes ont été réunies sous le nom .NET, sans qualificatif, ce qui explique le saut de numérotation qui évite la confusion avec .NET Framework 4.
Le point de compatibilité s'appelle .NET Standard : une spécification d'interface que les deux implémentent, et à laquelle une bibliothèque peut se conformer pour être utilisable des deux côtés.
Le déploiement
Deux modes, qui n'engagent pas la même chose.
Le déploiement dépendant du framework produit un livrable léger, qui suppose que le moteur d'exécution soit déjà installé sur la machine cible et dans une version compatible. C'est le mode qui convient à un parc maîtrisé.
Le déploiement autonome embarque le moteur d'exécution avec l'application. Le livrable pèse plusieurs dizaines de mégaoctets, ne dépend de rien, et fige la version du moteur. Une faille corrigée dans le moteur suppose alors de republier l'application.
L'image obtenue s'exécute à l'identique sur le poste du développeur, sur le serveur d'intégration et en production, ce qui supprime la catégorie d'incidents où le programme fonctionne d'un côté et pas de l'autre. Le dernier chapitre du bloc y revient.
Exercices type
Pourquoi le code C# n'est-il pas compilé directement en instructions machine ?
Pour deux raisons distinctes.
La portabilité : le code intermédiaire ne suppose ni processeur ni système particulier. La traduction finale a lieu sur la machine qui exécute, et le même assemblage sert partout.
Le contrôle à l'exécution : le moteur dispose des métadonnées complètes sur les types. Il peut donc vérifier les conversions, contrôler les accès mémoire et gérer automatiquement les allocations, ce qu'un binaire natif ne permet pas.
Le prix à payer est un démarrage plus lent, atténué par la compilation par paliers et par la compilation anticipée.
Qu'est-ce qui distingue un assemblage d'un simple fichier compilé ?
Le manifeste. Il contient le nom, la version, la culture, éventuellement une signature, et surtout la liste des dépendances avec les versions attendues.
Cette information rend l'assemblage autodescriptif : le moteur d'exécution sait ce qu'il doit charger, dans quelle version, sans configuration externe.
Elle permet aussi de refuser le démarrage en cas de dépendance manquante, plutôt que d'échouer plus tard sur un type introuvable, au milieu d'un traitement.
Qu'est-ce que l'enfer des DLL, et comment .NET Core y répond-il ?
Deux applications ont besoin de versions différentes de la même bibliothèque. Avec un dépôt partagé comme le GAC, installer l'une casse l'autre.
.NET Core supprime le dépôt partagé. Chaque application embarque les versions dont elle a besoin, dans son propre répertoire.
Le coût est l'espace disque et la duplication : une même bibliothèque existe en plusieurs exemplaires sur la machine. Le gain est qu'aucune installation ne peut plus casser une application déjà en place.
Le ramasse-miettes dispense-t-il de fermer un fichier ?
Non, et c'est une source d'erreur fréquente.
Il libère la mémoire devenue inaccessible. Une poignée de fichier, une connexion réseau ou un verrou ne sont pas de la mémoire : ce sont des ressources du système, limitées en nombre, que le moteur ne sait pas récupérer.
Un fichier laissé ouvert reste verrouillé, parfois jusqu'à l'arrêt du processus. Sur un traitement qui en ouvre des milliers, la limite du système est atteinte et le programme échoue sans rapport apparent avec sa cause.
La réponse de la plateforme est l'interface IDisposable et le bloc using, qui garantit la libération même en cas d'exception.
Quand choisir un déploiement autonome ?
Quand on ne maîtrise pas la machine cible, ou quand la version du moteur d'exécution doit être figée.
C'est le cas d'un logiciel distribué à des clients dont on ignore la configuration, ou d'une application dont le comportement dépend de particularités d'une version donnée.
Le mode dépendant du framework convient à un parc administré, où l'on sait ce qui est installé et où l'on peut mettre à jour le moteur pour toutes les applications en même temps.
Le compromis porte sur la sécurité : un livrable autonome fige le moteur, donc ses failles. Chaque correctif du moteur impose de reconstruire et de redistribuer l'application.
Une bibliothèque doit servir à un projet .NET Framework et à un projet .NET 8. Que faire ?
La cibler sur .NET Standard, la spécification d'interface que les deux implémentent.
Cela impose une contrainte : la bibliothèque ne peut employer que les interfaces couvertes par la version de .NET Standard retenue, donc pas les nouveautés propres à .NET 8 ni les particularités du Framework.
L'alternative consiste à produire plusieurs cibles depuis un même projet, ce qui permet d'exploiter les spécificités de chaque plateforme au prix d'un peu de compilation conditionnelle.
Le choix se fait sur ce que la bibliothèque a besoin d'appeler.
La méthode
- Sais nommer les étapes : source, code intermédiaire, assemblage, compilation à la volée, exécution.
- Cherche la version dans le manifeste avant de conclure à un problème de dépendance.
- Ne confonds pas .NET Framework et .NET moderne, malgré la proximité des noms.
- Libère explicitement tout ce qui n'est pas de la mémoire, avec
using. - Choisis le mode de déploiement selon la maîtrise que tu as de la machine cible.
- Cible .NET Standard pour une bibliothèque qui doit servir aux deux lignes.
En résumé
- Le compilateur C# produit du code intermédiaire, traduit en instructions machine à la volée par le moteur d'exécution.
- Un assemblage contient le code intermédiaire, les métadonnées et un manifeste qui déclare version et dépendances.
- Le CLR gère mémoire, types, sécurité et exceptions. Le code sous son contrôle est dit managé.
- Le GAC du .NET Framework a été supprimé dans .NET Core, ce qui règle l'enfer des DLL au prix de la duplication.
- .NET Framework est limité à Windows et en maintenance ; .NET Core puis .NET 5 et suivants sont multiplateformes.
- .NET Standard est le point de compatibilité entre les deux lignes.
- Le ramasse-miettes récupère la mémoire, pas les autres ressources.
- Déploiement dépendant du framework ou autonome : le second fige la version du moteur.
Et ensuite ? Avant d'écrire des classes, il faut savoir les décrire et les discuter. Le chapitre suivant présente les diagrammes UML employés en conception.