1.1 Un retour sur la console
1.2 Des fenêtres à la console
1.2.1 Console et fenêtre personnalisée
1.2.2 Fenêtre personnalisée sans console
1.2.3 Que fait Application.Run ?
1.2.4 Que faire avec Application.DoEvents ?
1.3 Un formulaire en C# est une fiche
1.4 Code C# engendré par le RAD pour un formulaire
1.5 Libération de ressources non managées
1.6 Comment la libération a-t-elle lieu dans le NetFrameWork ?
1.7 Peut-on influer sur cette la libération dans le NetFrameWork ?
1.8 Design Pattern de libération des ressources non managées
1.9 Un exemple utilisant la méthode Dispose d'un formulaire
1.10 L'instruction USING appelle Dispose( )
1.11 L'attribut [STAThread]
1. Les applications avec Interface Homme-Machine
Les exemples et les traitement qui suivent sont effectués sous l'OS windows avec NetFramWork 1.1., les paragraphes 1.3, 1.4, ... , 1.10 expliquent le contenu du code généré automatiquement par Visual Studio ou C# Builder pour développer une application fenêtrée.
Le NetFrameWork est subdivisé en plusieurs espace de nom, l'espace de noms System contient plusieurs classes et est subdivisé lui-même en plusieurs sous-espace de noms :
etc ...
L'espace des noms System.Windows.Forms est le domaine privilégié du NetFrameWork dans lequel l'on trouve des classes permettant de travailler sur des applications fenêtrées.
La classe Form de l'espace des noms System.Windows.Forms permet de créer une fenêtre classique avec barre de titre, zone client, boutons de fermeture, de zoom...
En C#, Il suffit d'instancier un objet de cette classe pour obtenir une fenêtre classique qui s'affiche sur l'écran.
1.1 un retour sur la console
Le code C# peut tout comme le code Java être écrit avec un éditeur de texte rudimentaire du style bloc-note, puis être compilé directement à la console par appel au compilateur csc.exe.
Soient par exemple dans un dossier temp du disque C: , deux fichiers :
![]()
Le fichier "_exoconsole.bat" contient la commande système permettant d'appeller le compilateur C#.
Le fichier "_exoconsole.cs" le programme source en C#.
Construction de la commande de compilation "_exoconsole.bat" :
On indique d'abord le chemin (variable path) du répertoire où se trouve le compilateur csc.exe, puis on lance l'appel au compilateur avec ici , un nombre minimal de paramètres :
Attributs et paramètres de la commande
fonction associée
set path = C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322
Le chemin absolu permettant d'accéser au dossier contenant le compilateur C# (csc.exe)
/t:exe
Indique que nous voulons engendrer une exécutable console (du code MSIL)
/out: _exo.exe
Indique le nom que doit porter le fichier exécutable MSIL après compilation
_exoconsole.cs
Le chemin complet du fichier source C# à compiler (ici il est dans le même répertoire que la commande, seul le nom du fichier suffit)
Texte de la commande dans le Bloc-note :
![]()
Nous donnons le contenu du fichier source _exoconsole.cs à compiler :
![]()
Ce programme affiche le mot Bonjour suivit de l'exécution de la boucle sur 5 itérations.
On lance l'invite de commande de Windows ici dans le répertoire c:\temp :
![]()
On tape au clavier et l'on exécute la commande "_exoconsole.bat" :
![]()
Elle appelle le compilateur csc.exe qui effectue la compilation du programme _exoconsole.cs sans signaler d'erreur particulière et qui a donc engendré une fichier MSIL nommé _exo.exe :
![]()
Lançons par un double click l'exécution du programme _exo.exe qui vient d'être engendré :
![]()
Nous constatons que l'exécution par le CLR du fichier _exo.exe a produit le résultat escompté c'est à dire l'affichage du mot Bonjour suivit de l'exécution de la boucle sur 5 itérations.
Afin que le lecteur soit bien convaincu que nous sommes sous NetFramework et que les fichiers exécutables ne sont pas du binaire exécutable comme jusqu'à présent sous Windows, mais des fichiers de code MSIL exécutable par le CLR, nous passons le fichier _exo.exe au désassembleur ildasm par la commande "ildasm.bat".
Le désassembleur MSIL Disassembler (Ildasm.exe) est un utilitaire inclus dans le kit de développement .NET Framework SDK, il est de ce fait utilisable avec tout langage de .Net dont C#. ILDasm.exe analyse toutes sortes d'assemblys .NET Framework .exe ou .dll et présente les informations dans un format explicite. Cet outil affiche bien plus que du code MSIL (Microsoft Intermediate Language) ; il présente également les espaces de noms et les types, interfaces comprises.
Voici l'inspection du fichier _exo.exe par ildasm :
![]()
Demandons à ildasm l'inspection du code MSIL engendré pour la méthode Main( ) :
Nous avons coloré en rouge les commentaires d'instructions sources
Nous avons coloré en bleu les instructions CIL engendrées
Exemple::methodeMain void( )
.method private hidebysig static void Main( ) cil managed
{
.entrypoint
// Code size 51 (0x33)
.maxstack 2
.locals init ([0] int32 i)
.language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'
// Source File 'c:\temp\_exoconsole.cs'
//000007: System.Console.WriteLine("Bonjour");
IL_0000: ldstr "Bonjour"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
//000008: for ( int i=1; i<6; i++ )
IL_000a: ldc.i4.1
IL_000b: stloc.0
IL_000c: br.s IL_0028
//000009: System.Console.WriteLine( "i = "+i.ToString( ) );
IL_000e: ldstr "i = "
IL_0013: ldloca.s i
IL_0015: call instance string [mscorlib]System.Int32::ToString()
IL_001a: call string [mscorlib]System.String::Concat(string,string)
IL_001f: call void [mscorlib]System.Console::WriteLine(string)
//000008: for ( int i=1; i<6; i++ )
IL_0024: ldloc.0
IL_0025: ldc.i4.1
IL_0026: add
IL_0027: stloc.0
IL_0028: ldloc.0
IL_0029: ldc.i4.6
IL_002a: blt.s IL_000e
//000009: System.Console.WriteLine( "i = "+i.ToString( ) );
//000010: System.Console.ReadLine( );
IL_002c: call string [mscorlib]System.Console::ReadLine()
IL_0031: pop
//000011: }
IL_0032: ret
} // end of method Exemple::Main
1.2 Des fenêtres à la console
On peut donc de la même façon compiler et exécuter à partir de la console, des programmes C# contenant des fenêtres, comme en java il est possible d'exécuter à partir de la console des applications contenant des Awt ou des Swing, idem en Delphi. Nous proposons au lecteur de savoir utiliser des programmes qui allient la console à une fenêtre classique, ou des programmes qui ne sont formés que d'une fenêtre classique (à minima).
1.2.1 fenêtre console et fenêtre personnalisée ensembles
Ecrivons le programme C# suivant dans un fichier que nous nommons "_exowin.cs" :
![]()
Ce programme "_exowin.cs" utilise la classe Form et affiche une fenêtre de type Form :
La première instruction instancie un objet nommé fiche de la classe Form
Form fiche = new Form( ) ;
La fenêtre fiche est ensuite "initialisée" et "lancée" par l'instruction
Application.Run(fiche) ;
On construit une commande nommée _exowin.bat, identique à celle du paragraphe précédent, afin de lancer la compilation du programme _exowin.cs, nous décidons de nommer _exow.exe l'exécutable MSIL obtenu après compilation.
contenu fichier de commande _exowin.bat :
set path=C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322
csc /t:exe /out:_exow.exe _exowin.cs
On exécute ensuite la commande _exowin.bat dans une invite de commande de Windows :
![]()
Cette commande a généré comme précédemment l'exécutable MSIL nommé ici _exow.exe dans le dossier c:\temp, nous exécutons le programme _exow.exe et nous obtenons l'affichage d'une fenêtre de console et de la fenêtre fiche :
![]()
Remarque:
L'attribut /debug+ rajouté ici, permet d'engendrer un fichier _exo.pdb qui contient des informations de déboguage utiles à ildasm.
1.2.2 fenêtre personnalisée sans fenêtre console
Si nous ne voulons pas voir apparaître de fenêtre de console mais seulement la fenêtre fiche, il faut alors changer dans le paramétrage du compilateur l'attribut target. De la valeur csc /t:exe il faut passer à la valeur csc /t:winexe :
Nouveau fichier de commande _exowin.bat :
set path=C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322
csc /t:winexe /out:_exow.exe _exowin.cs
Nous compilons en lançant la nouvelle commande _exowin.bat puis nous exécutons le nouveau programme _exow.exe. Nous obtenons cette fois-ci l'affichage d'une seule fenêtre (la fenêtre de console a disparu) :
![]()
Cette fenêtre est un peu trop terne, nous pouvons travailler en mode console sur la fiche Form, afin par exemple de mettre un texte dans la barre de titre et de modifier sa couleur de fond.
Nous écrivons pour cela le nouveau programme C# dans le fichier "_exowin.cs" :
![]()
Nous compilons en lançant la commande _exowin.bat puis nous exécutons le nouveau programme _exow.exe et nous obtenons l'affichage de la fenêtre fiche avec le texte "Exemple de Form" dans la barre de titre et sa couleur de fond marron-rosé (Color.RosyBrown) :
![]()
Consultons à titre informatif avec ildasm le code MSIL engendré pour la méthode Main( ) :
Nous avons coloré en rouge les commentaires d'instructions sources
Nous avons coloré en bleu les instructions CIL engendrées
Exemple::methodeMain void( )
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 35 (0x23)
.maxstack 2
.locals init ([0] class [System.Windows.Forms]System.Windows.Forms.Form fiche)
.language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'
// Source File 'c:\temp\_exowin.cs'
//000008: Form fiche = new Form( );
IL_0000: newobj instance void [System.Windows.Forms]System.Windows.Forms.Form::.ctor()
IL_0005: stloc.0
//000009: fiche.Text="Exemple de Form";
IL_0006: ldloc.0
IL_0007: ldstr "Exemple de Form"
IL_000c: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
//000010: fiche.BackColor=System.Drawing.Color.RosyBrown;
IL_0011: ldloc.0
IL_0012: call valuetype [System.Drawing]System.Drawing.Color [System.Drawing]System.Drawing.Color::get_RosyBrown()
IL_0017: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_BackColor(valuetype [System.Drawing]System.Drawing.Color)
//000011: Application.Run(fiche);
IL_001c: ldloc.0
IL_001d: call void [System.Windows.Forms]System.Windows.Forms.Application::Run(class [System.Windows.Forms]System.Windows.Forms.Form)
//000012: }
IL_0022: ret
} // end of method Exemple::Main
1.2.3 Que fait Application.Run( fiche )
Comme les fenêtres dans Windows ne sont pas des objets ordinaires, pour qu'elles fonctionnent correctement vis à vis des messages échangés entre la fenêtre et le système, il est nécessaire de lancer une boucle d'attente de messages du genre :
tantque non ArrêtSysteme faire
si événement alors
construire Message ;
si Message ¹ ArrêtSysteme alors
reconnaître la fenêtre à qui est destinée ce Message;
distribuer ce Message
fsi
fsi
ftant
La documentation technique indique que l'une des surcharges de la méthode de classe Run de la classe Application "public static void Run( Form mainForm );" exécute une boucle de messages d'application standard sur le thread en cours et affiche la Form spécifiée. Nous en déduisons que notre fenêtre fiche est bien initialisée et affichée par cette méthode Run.
Observons à contrario ce qui se passe si nous n'invoquons pas la méthode Run et programmons l'affichage de la fiche avec sa méthode Show :
using System;
using System.Windows.Forms;
namespace ApplicationTest
{
public class Exemple
{
static void Main( ) {
Form fiche = new Form( );
fiche.Text="Exemple de Form";
fiche.BackColor=System.Drawing.Color.RosyBrown;
fiche.Show( );
}
}
}
Lorsque nous compilons puis exécutons ce programme la fiche apparaît correctement (titre et couleur) d'une manière fugace car elle disparaît aussi vite qu'elle est apparue. En effet le prgramme que nous avons écrit est correct :
Ligne d'instruction du programme
Que fait le CLR
{
il initialise l'exécution de la méthode main
Form fiche = new Form( );
il instancie une Form nommée fiche
fiche.Text="Exemple de Form";
il met un texte dans le titre de fiche
fiche.BackColor=System.Drawing.Color.RosyBrown;
il modifie la couleur du fond de fiche
fiche.Show( );
il rend la fiche visible
}
fin de la méthode Main et donc tout est détruit et libéré et le processus est terminé.
La fugacité de l'affichage de notre fenêtre fiche est donc normale, puisqu'à peine créée la fiche a été détruite.
Si nous voulons que notre objet de fiche persiste sur l'écran, il faut simuler le comportement de la méthode classe Run, c'est à dire qu'il nous faut écrire une boucle de messages.
1.2.4 Que faire avec Application.DoEvents( )
Nous allons utiliser la méthode de classe DoEvents( ) de la classe Application qui existe depuis Visual Basic 2, et qui permet de traiter tous les messages Windows présents dans la file d'attente des messages de la fenêtre (elle passe la main au système d'un façon synchrone) puis revient dans le programme qui l'a invoquée (identique à processMessages de Delphi).
![]()
Nous créeons artificiellement une boucle en apparence infinie qui laisse le traitement des messages s'effectuer et qui attend qu'on lui demande de s'arrêter grâce par l’intermédiaire d’un booléen stop dont la valeur change par effet de bord grâce à DoEvents :
static bool stop = false;
while (!stop) Application.DoEvents( );
![]()
Il suffit que lorsque DoEvents( ) s'exécute l'une des actions de traitement de messages provoque la mise du booléen stop à true pour que la boucle s'arrête et que le processus se termine.
Choisissons une telle action par exemple lorsque l'utilisateur clique sur le bouton de fermeture de la fiche, la fiche se ferme et l'événement closed est déclenché et DoEvents( ) revient dans la boucle d'attente while (!stop) Application.DoEvents( ); au tour de boucle suivant. Si lorsque l'événement close de la fiche a lieu nous en profitons pour mettre le booléen stop à true, dès le retour de DoEvents( ) le prochain tour de boucle arrêtera l'exécution de la boucle et le corps d'instruction de main continuera à s'exécuter séquentiellement jusqu'à la fin (ici on arrêtera le processus).
Ci-dessous le programme C# à mettre dans le fichier "_exowin.cs" :
using System;
using System.Windows.Forms;
namespace ApplicationTest
{
public class Exemple
{
static bool stop = false; // le drapeau d'arrêt de la boucle d'attente
static void fiche_Closed (object sender, System.EventArgs e)
{ // le gestionnaire de l'événement closed de la fiche
stop = true;
}
static void Main( ) {
System.Windows.Forms.Button button1 = new System.Windows.Forms.Button( );
Form fiche = new Form( );
fiche.Text="Exemple de Form";
fiche.BackColor=System.Drawing.Color.RosyBrown;
fiche.Closed += new System.EventHandler(fiche_Closed); // abonnement du gestionnaire
fiche.Show( );
while (!stop) Application.DoEvents( ); // boucle d'attente
//... suite éventuelle du code avant arrêt
}
}
}
Lorsque nous compilons puis exécutons ce programme la fiche apparaît correctement et reste présente sur l'écran :
![]()
Elle se ferme et disparaît lorsque nous cliquons sur le bouton de fermeture.
On peut aussi vouloir toujours en utilisant la boucle infinie qui laisse le traitement des messages s'effectuer ne pas se servir d'un booléen et continuer après la boucle, mais plutôt essayer d'interrompre et de terminer l'application directement dans la boucle infinie sans exécuter la suite du code. La classe Application ne permet pas de terminer le processus.
Attention à l'utilisation de la méthode Exit de la classe Application qui semblerait être utilisable dans ce cas, en effet cette méthode arrête toutes les boucles de messages en cours sur tous les threads et ferme toutes les fenêtres de l'application; mais cette méthode ne force pas la fermeture de l'application.
Pour nous en convaincre compilons et exécutons le programme ci-après dans lequel l'événement fiche_Closed appelle Application.Exit( )
Ci-dessous le programme C# à mettre dans le fichier "_exowin.cs" :
using System;
using System.Windows.Forms;
namespace ApplicationTest
{
public class Exemple
{
static void fiche_Closed (object sender, System.EventArgs e)
{ // le gestionnaire de l'événement closed de la fiche
Application.Exit( ); // on ferme la fenêtre, mais on ne termine pas le processus
}
static void Main( ) {
System.Windows.Forms.Button button1 = new System.Windows.Forms.Button( );
Form fiche = new Form( );
fiche.Text="Exemple de Form";
fiche.BackColor=System.Drawing.Color.RosyBrown;
fiche.Closed += new System.EventHandler(fiche_Closed); // abonnement du gestionnaire
fiche.Show( );
while (true) Application.DoEvents( ); // boucle infinie
}
}
}
Lorsque nous compilons puis exécutons ce programme la fiche apparaît correctement et reste présente sur l'écran, puis lorsque nous fermons la fenêtre comme précédamment, elle disparaît, toutefois le processus _exow.exe est toujours actif (la boucle tourne toujours, mais la fenêtre a été fermée) en faisant apparaître le gestionnaire des tâches de Windows à l'onglet processus nous voyons qu'il est toujours présent dans la liste des processus actifs. Si nous voulons l'arrêter il faut le faire manuellement comme indiqué ci-dessous :
![]()
Comment faire pour réellement tout détruire ?
Il faut pouvoir détruire le processus en cours (en prenant soin d'avoir tout libéré avant si nécessaire), pour cela le NetFrameWork dispose d'une classe Process qui permet l'accès à des processus locaux ainsi que distants, et vous permet de démarrer et d'arrêter des processus systèmes locaux.
Nous pouvons connaître le processus en cours d'activation (ici, c'est notre application_exow.exe) grâce à la méthode de classe GetCurrentProcess et nous pouvons "tuer" un processus grâce à la méthode d'instance Kill :
static void fiche_Closed (object sender, System.EventArgs e)
{ // le gestionnaire de l'événement closed de la fiche
System.Diagnostics.Process ProcCurr = System.Diagnostics.Process.GetCurrentProcess();
ProcCurr.Kill( );
}
Ci-dessous le programme C# à mettre dans le fichier "_exowin.cs" :
using System;
using System.Windows.Forms;
namespace ApplicationTest
{
public class Exemple
{
static void fiche_Closed (object sender, System.EventArgs e)
{ // le gestionnaire de l'événement closed de la fiche
System.Diagnostics.Process ProcCurr = System.Diagnostics.Process.GetCurrentProcess( );
ProcCurr.Kill( );
}
static void Main( ) {
System.Windows.Forms.Button button1 = new System.Windows.Forms.Button( );
Form fiche = new Form( );
fiche.Text="Exemple de Form";
fiche.BackColor=System.Drawing.Color.RosyBrown;
fiche.Closed += new System.EventHandler(fiche_Closed); // abonnement du gestionnaire
fiche.Show( );
while (true) Application.DoEvents( ); // boucle infinie
}
}
}
Après compilation, exécution et fermeture en faisant apparaître le gestionnaire des tâches de Windows à l'onglet processus nous voyons que le processus a disparu de la liste des processus actifs du système. Nous avons donc bien interrompu la boucle infinie.
Toutefois la console n'est pas l'outil préférentiel de C# dans le sens où C# est l'outil de développement de base de .Net et que cette architecture a vocation à travailler essentiellement avec des fenêtres.
Dans ce cas nous avons tout intérêt à utiliser un RAD visuel C# pour développer ce genre d'applications (comme l'on utilise Delphi pour le développement d'IHM en pascal objet). Une telle utilisation nous procure le confort du développement visuel, la génération automatique d'une bonne partie du code répétitif sur une IHM, l'utilisation et la réutilisation de composants logiciels distribués sur le net.
RAD utilisables
Visual Studio de microsoft contient deux RAD de développement pour .Net, VBNet (fondé sur Visual Basic réellement objet et entièrement rénové) et Visual C# (fondé sur le langage C#), parfaitement adapté à .Net. (prix très réduit pour l'éducation)
C# Builder de Borland reprenant les fonctionnalités de Visual C# dans Visual Studio, avec un intérêt supplémentaire pour un étudiant ou un apprenant : une version personnelle gratuite est téléchargeable.
Le monde de l'open source construit un produit nommé sharpDevelop qui devrait à terme fournir à tous gratuitement aussi les mêmes fonctions.
1.3 un formulaire en C# est une fiche
Les fiches ou formulaires C# représentent l'interface utilisateur (IHM) d'une application sous l'apparence d'une fenêtre visuelle. Comme les deux environnements RAD, Visual studio C# de Microsoft et C# Builder de Borland permettent de concevoir visuellement des applications avec IHM, nous dénommerons l'un ou l'autre par le terme général RAD C#.
Etant donné la disponibilité gratuite du RAD C# Builder en version personnelle (très suffisante pour déjà écrire de bonnes applications) nous illustrerons tous nos exemples avec ce RAD.
Voici l'apparence d'un formulaire (ou fiche) dans le RAD C# Builder en mode conception :
![]()
La fiche elle-même est figurée par l'image ci-dessous retaillable à volonté à partir de clicqué glissé sur l'une des huits petites "poignées carrées" situées aux points cardinaux de la fiche :
![]()
Ces formulaires sont en faits des objets d'une classe nommée Form de l'espace des noms System.Windows.Forms. Ci-dessous la hiérarchie d'héritage de Object à Form :
System.Object
System.MarshalByRefObject
System.ComponentModel.Component
System.Windows.Forms.Control
System.Windows.Forms.ScrollableControl
System.Windows.Forms.ContainerControl
System.Windows.Forms.Form
La classe Form est la classe de base de tout style de fiche (ou formulaire) à utiliser dans votre application (statut identique à TForm dans Delphi) : fiche de dialogue, sans bordure etc..
Les différents styles de fiches sont spécifiés par l'énumération FormBorderStyle :
public Enum FormBorderStyle {Fixed3D, FixedDialog, FixedSingle, FixedToolWindow, None, Sizable, SizableToolWindow }
Dans un formulaire, le style est spécifié par la propriété FormBorderStyle de la classe Form :
public FormBorderStyle FormBorderStyle {get; set;}
Toutes les propriétés en lecture et écriture d'une fiche sont accessibles à travers l'inspecteur d'objet qui répercute immédiatement en mode conception toute modification. Certaines provoquent des changements visuels d'autres non :
1°) changeons le nom d'identificateur de notre fiche dans le programme en modifiant la propriété Name qui vaut par défaut WinForm :
![]()
Le RAD construit automatiquement notre fiche principale comme une classe héritée de la classe Form et l'appelle WinForm :
public class WinForm : System.Windows.Forms.Form
{ ... }
Après modification de la propriété Name par le texte Form1, nous obtenons :
![]()
La classe de notre formulaire s'appelle désormais Form1, mais son aspect visuel est resté le même :
![]()
2°) Par exemple sélectionnons dans l'inspecteur d'objet de C# Builder, la propriété FormBorderStyle ( le style par défaut est FormBorderStyle.Sizable ) modifions la à la valeur None et regardons dans l'onglet conception la nouvelle forme de la fiche :
![]()
L'aspect visuel du formulaire a changé.
1.4 code C# engendré par le RAD pour un formulaire
Après avoir remis grâce à l'inspecteur d'objet, la propriété FormBorderStyle à sa valeur Sizable et remis le Name à sa valeur initiale WinForm, voyons maintenant en supposant avoir appelé notre application ProjApplication0 ce que C# Builder a engendré comme code source que nous trouvons dans l'onglet code pour notre formulaire :
![]()
L'intégralité du code proposé par C# Builder est sauvegardé dans un fichier nommé WinForm.cs
Allure général du contenu de ce fichier affiché dans l'onglet code :
![]()
La classe WinForm contient en premièer analyse 3 méthodes :
![]()
Premier éléments explicatifs de l'analyse du code :
La méthode
correspond
public WinForm ( ) {
...
}
au constructeur d'objet de la classe WinForm et que la méthode
static void Main( ) {
Application.Run (new WinForm ( ));
}
au point d'entrée d'exécution de l'application, son corps contient un appel de la méthode statique Run de la classe Application, elle instancie un objet "new WinForm ( )"de classe WinForm passé en paramètre à la méthode Run : c'est la fiche principale de l'application.
Application.Run (new WinForm ( ));
La classe Application (semblable à TApplication de Delphi) fournit des membres statiques (propriétés et méthodes de classes) pour gérer une application (démarrer, arrêter une application, traiter des messages Windows), ou d'obtenir des informations sur une application. Cette classe est sealed et ne peut donc pas être héritée.
La méthode Run de la classe Application dont voici la signature :
public static void Run( ApplicationContext context );
Exécute une boucle de messages d'application standard sur le thread en cours, par défaut le paramètre context écoute l'événement Closed sur la fiche principale de l'application et dès lors arrête l'application.
Pour les connaisseurs de Delphi , le démarrage de l'exécution s'effectue dans le programme principal :
program Project1;
uses Forms, Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm (WinForm , Form1);
Application.Run;
end.
Pour les connaisseurs des Awt et des Swing de Java, cette action C# correspond aux lignes suivantes :
Java2 avec Awt
class WinForm extends Frame {
public WinForm ( ) {
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
}
protected void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e);
if(e.getID( ) == WindowEvent.WINDOW_CLOSING) {
System.exit(0); }
}
public static void main(String[] x ) {
new WinForm ( );
}
}
Java2 avec Swing
class WinForm extends JFrame {
public WinForm ( ) {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] x ) {
new WinForm ( );
}
}
Lorsque l'on regarde de plus près le code de la classe WinForm situé dans l'onglet code on se rend compte qu'il existe une ligne en grisé entre la méthode Dispose et la méthode main :
![]()
Il s'agit en fait de code replié (masqué).
Voici le contenu exact de l'onglet code avec sa zone de code replié :
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace ProjApplication0
{
/// <summary>
/// Description Résumé de Form1.
/// </summary>
public class WinForm : System.Windows.Forms.Form
{
/// <summary>
/// Variable requise par le concepteur.
/// </summary>
private System.ComponentModel.Container components = null;
public WinForm ( )
{
//
// Requis pour la gestion du concepteur Windows Form
//
InitializeComponent( );
//
// TODO: Ajouter tout le code du constructeur après l'appel de InitializeComponent
//
}
/// <summary>
/// Nettoyage des ressources utilisées.
/// </summary>
protected override void Dispose (bool disposing)
{
if (disposing) {
if (components != null) {
components.Dispose( );
}
}
base.Dispose(disposing);
}
![]()
/// <summary>
/// Le point d'entrée principal de l'application.
/// </summary>
[STAThread]
static void Main( )
{
Application.Run(new WinForm ( ));
}
}
}
Si nous le déplions et nous voyons apparaître la méthode privée InitializeComponent( ) contenant du code qui a manifestement été généré directement. En outre cette méthode est appelée dans le constructeur d'objet WinForm :
![]()
Nous déplions la région Code généré par le concepteur Windows Form :
#region Code généré par le concepteur Windows Form
/// <summary>
/// Méthode requise pour la gestion du concepteur - ne pas modifier
/// le contenu de cette méthode avec l'éditeur de code.
/// </summary>
private void InitializeComponent( )
{
//
// WinForm
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(232, 157);
this.Name = "WinForm";
this.Text = "WinForm";
}
#endregion
Essayons de voir comment une manipulation visuelle engendre des lignes de code, pour cela modifions dans l'inspecteur d'objet deux propriétés FormBorderStyle et BackColor, la première est mise à None la seconde qui indique la couleur du fond de la fiche est mise à LightSalmon :
![]()
Consultons après cette opération le contenu du nouveau code généré, nous trouvons deux nouvelles lignes de code correspondant aux nouvelles actions visuelles effectuées (les nouvelles lignes sont figurées en rouge ) :
#region Code généré par le concepteur Windows Form
/// <summary>
/// Méthode requise pour la gestion du concepteur - ne pas modifier
/// le contenu de cette méthode avec l'éditeur de code.
/// </summary>
private void InitializeComponent( )
{
//
// WinForm
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.BackColor = System.Drawing.Color.LightSalmon;
this.ClientSize = new System.Drawing.Size(232, 157);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Name = "WinForm";
this.Text = "WinForm";
}
#endregion
1.5 Libération de ressources non managées
Dans le code engendré par Visual studio ou C# Builder, nous avons laissé de côté la méthode Dispose :
protected override void Dispose (bool disposing) {
if (disposing) {
if (components != null) {
components.Dispose( );
}
}
base.Dispose( disposing );
}
Pour comprendre son utilité, il nous faut avoir quelques lumières sur la façon que NetFrameWork a de gérer les ressources, rappelons que le CLR exécute et gére le code administré c'est à dire qu'il vérifie la validité de chaque action avant de l'exécuter. Le code non administré ou ressource non managée en C# est essentiellement du code sur les pointeurs qui doivent être déclarés unsafe pour pouvoir être utilisés, ou bien du code sur des fichiers, des flux , des handles .
La méthode Dispose existe déjà dans la classe mère System.ComponentModel.Component sous forme de deux surcharges avec deux signatures différentes. Elle peut être utile si vous avez mobilisé des ressources personnelles ( on dit aussi ressources non managées ) et que vous souhaitiez que celles-ci soient libérées lors de la fermeture de la fiche :
classe : System.ComponentModel.Component
méthode : public virtual void Dispose( );
Libère toutes les ressources utilisées par Component.
méthode : protected virtual void Dispose( bool disposing );
Libère uniquement les ressources non managées utilisées par Component..
ou
Libère toutes les ressources utilisées par Component.
Selon la valeur de disposing
- disposing = true pour libérer toutes les ressources (managées et non managées) ;
- disposing = false pour libérer uniquement les ressources non managées.
Remarque-1
Notons que pour le débutant cette méthode ne sera jamais utilisée et peut être omise puisqu'il s'agit d'une surcharge dynamique de la méthode de la classe mère.
Remarque-2
Il est recommandé par Microsoft, qu'un objet Component libère des ressources explicitement en appelant sa méthode Dispose sans attendre une gestion automatique de la mémoire lors d'un appel implicite au Garbage Collector.
Si nous voulons comprendre comment fonctionne le code engendré pour la méthode Dispose, il nous faut revenir à des éléments de base de la gestion mémoire en particulier relativement à la libération des ressources par le ramasse-miettes (garbage collector).
1.6 Comment la libération a-t-elle lieu dans le NetFrameWork ?
La classe mère de la hiérarchie dans le NetFrameWork est la classe System.Object, elle possède une méthode virtuelle protected Finalize, qui permet de libérer des ressources et d'exécuter d'autres opérations de nettoyage avant que Object soit récupéré par le garbage collecteur GC.
![]()
Lorsqu'un objet devient inaccessible il est automatiquement placé dans la file d'attente de finalisation de type FIFO, le garbage collecteur GC, lorsque la mémoire devient trop basse, effectue son travail en parcourant cette file d'attente de finalisation et en libérant la mémoire occupée par les objets de la file par appel à la méthode Finalize de chaque objet.
Donc si l'on souhaite libérer des ressources personnalisées, il suffit de redéfinir dans une classe fille la méthode Finalize( ) et de programmer dans le corps de la méthode la libération de ces ressources.
En C# on pourrait écrire pour une classe MaClasse :
protected override void Finalize( ) {
try {
// libération des ressources personnelles
}
finally
{
base.Finalize( ); // libération des ressources du parent
}
}
Mais syntaxiquement en C# la méthode Finalize n'existe pas et le code précédent, s'il représente bien ce qu'il faut faire, ne sera pas accepté par le compilateur. En C# la méthode Finalize s'écrit comme un destructeur de la classe MaClasse :
~MaClasse( ) {
// libération des ressources personnelles
}
1.7 Peut-on influer sur cette la libération dans le NetFrameWork ?
Le processus de gestion de la libération mémoire et de sa récupération est entièrement automatisé dans le CLR, mais selon les nécessités on peut avoir le besoin de gérer cette désallocation : il existe pour cela, une classe System.GC qui autorise le développeur à une certaine dose de contrôle du garbage collector.
Par exemple, vous pouvez empêcher explicitement la méthode Finalize d'un objet figurant dans la file d'attente de finalisation d'être appelée, (utilisation de la méthode : public static void SuppressFinalize( object obj );)
![]()
Vous pouvez aussi obliger explicitement la méthode Finalize d'un objet figurant dans la file d'attente de finalisation mais contenant GC.SuppressFinalize(...) d'être appelée, ( utilisation de la méthode : public static void ReRegisterForFinalize( object obj ); ).
Microsoft propose deux recommandations pour la libération des ressources :
Il est recommandé d'empêcher les utilisateurs de votre application d'appeler directement la méthode Finalize d'un objet en limitant sa portée à protected.
Il est vivement déconseillé d'appeler une méthode Finalize pour une autre classe que votre classe de base directement à partir du code de votre application. Pour supprimer correctement des ressources non managées, il est recommandé d'implémenter une méthode Dispose ou Close publique qui exécute le code de nettoyage nécessaire pour l'objet.
Microsoft propose des conseils pour écrire la méthode Dispose :
1- La méthode Dispose d'un type doit libérer toutes les ressources qu'il possède.
2- Elle doit également libérer toutes les ressources détenues par ses types de base en appelant la méthode Dispose de son type parent. La méthode Dispose du type parent doit libérer toutes les ressources qu'il possède et appeler à son tour la méthode Dispose de son type parent, propageant ainsi ce modèle dans la hiérarchie des types de base.
3- Pour que les ressources soient toujours assurées d'être correctement nettoyées, une méthode Dispose doit pouvoir être appelée en toute sécurité à plusieurs reprises sans lever d'exception.
4- Une méthode Dispose doit appeler la méthode GC.SuppressFinalize de l'objet qu'elle supprime.
5- La méthode Dispose doit être à liaison statique
1.8 Design Pattern de libération des ressources non managées
Le NetFrameWork propose une interface IDisposable ne contenant qu'une seule méthode : Dispose
![]()
Rappel
Il est recommandé par Microsoft, qu'un objet Component libère des ressources explicitement en appelant sa méthode Dispose sans attendre une gestion automatique de la mémoire lors d'un appel implicite au Garbage Collector. C'est ainsi que fonctionnent tous les contrôles et les composants de NetFrameWork. Il est bon de suivre ce conseil car dans le modèle de conception fourni ci-après, la libération d'un composant fait libérér en cascade tous les éléments de la hiérarchie sans les mettre en liste de finalisation ce qui serait une perte de mémoire et de temps pour le GC.
Design Pattern de libération dans la classe de base
Voici pour information, proposé par Microsoft, un modèle de conception (Design Pattern) d'une classe MaClasseMere implémentant la mise à disposition du mécanisme de libération des ressources identique au NetFrameWork :
public class MaClasseMere : IDisposable {
private bool disposed = false;
// un exemple de ressource managée : un composant
private Component Components = new Component( );
// ...éventuellement des ressources non managées (pointeurs...)
public void Dispose( ) {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (!this.disposed) {
if (disposing) {
Components.Dispose( ); // libère le composant
// libère les autres éventuelles ressources managées
}
// libère les ressources non managées :
//... votre code de libération
disposed = true;
}
~MaClasseMere ( ) { // finaliseur par défaut
Dispose(false);
}
}
Ce modèle n'est présenté que pour mémoire afin de bien comprendre le modèle pour une classe fille qui suit et qui correspond au code généré par le RAD C# .
Design Pattern de libération dans une classe fille
Voici proposé le modèle de conception simplifié (Design Pattern) d'une classe MaClasseFille descendante de MaClasseMere, la classe fille contient une ressource de type System.ComponentModel.Container
public class MaClasseFille : MaClasseMere {
private System.ComponentModel.Container components = null ;
public MaClasseFille ( ) {
// code du constructeur....
}
protected override void Dispose(bool disposing) {
if (disposing) {
if (components != null) { // s'il y a réellemnt une ressource
components.Dispose( ); // on Dispose cette ressource
}
// libération éventuelle d'autres ressources managées...
}
// libération des ressources personnelles (non managées)...
base.Dispose(disposing); // on Dispose dans la classe parent
}
}
Information NetFrameWork sur la classe Container :
System.ComponentModel.Container
La classe Container est l'implémentation par défaut pour l'interface IContainer, une instance s'appelle un conteneur.
Les conteneurs sont des objets qui encapsulent et effectuent le suivi de zéro ou plusieurs composants qui sont des objets visuels ou non de la classe System.ComponentModel.Component.
Les références des composants d'un conteneur sont rangées dans une file FIFO, qui définit également leur ordre dans le conteneur.
La classe Container suit le modèle de conception mentionné plus haut quant à la libération des ressources managées ou non. Elle possède deux surcharges de Dispose implémentées selon le Design Pattern : la méthode protected virtual void Dispose( bool disposing); et la méthode public void Dispose( ). Cette méthode libère toutes les ressources détenues par les objets managés stockés dans la FIFO du Container. Cette méthode appelle la méthode Dispose( ) de chaque objet référencé dans la FIFO.
Les formulaires implémentent IDisposable :
La classe System.Windows.Forms.Form hérite de la classe System.ComponentModel.Component, or si nous consultons la documentation technique nous constatons que la classe Component en autres spécifications implémente l'interface IDisposable :
public class Component : MarshalByRefObject, IComponent, IDisposable
Donc tous les composants de C# sont construits selon le Design Pattern de libération :
La classe Component implémente le Design Pattern de libération de la classe de base et toutes les classe descendantes dont la classe Form implémente le Design Pattern de libération de la classe fille.
Nous savons maintenant à quoi sert la méthode Dispose dans le code engendré par le RAD, elle nous propose une libération automatique des ressources de la liste des composants que nous aurions éventuellement créés :
// Nettoyage des ressources utilisées :
protected override void Dispose (bool disposing)
{
if (disposing) { // Nettoyage des ressources managées
}
if (components != null) {
components.Dispose( );
}
// Nettoyage des ressources non managées
base.Dispose(disposing);
}
1.9 Un exemple utilisant la méthode Dispose d'un formulaire
Supposons que nous avons construit un composant personnel dans une classe UnComposant qui hérite de la classe Component selon le Design Pattern précédent, et que nous avons défini cette classe dans le namespace ProjApplication0 :
System.ComponentModel.Component
|__ProjApplication0.UnComposant
Nous voulons construire une application qui est un formulaire et nous voulons créer lors de l'initialisation de la fiche un objet de classe UnComposant que notre formulaire utilisera.
A un moment donné notre application ne va plus se servir du tout de notre composant, si nous souhaitons gérer la libération de la mémoire allouée à ce composant, nous pouvons :
- Soit attendre qu'il soit éligible au GC, en ce cas la mémoire sera libérée lorsque le GC le décidera,
- Soit le recenser auprès du conteneur de composants components (l'ajouter dans la FIFO de components si le conteneur a déjà été créé ). Sinon nous créons ce conteneur et nous utilisons la méthode d'ajout (Add) de la classe System.ComponentModel.Container pour ajouter notre objet de classe UnComposant dans la FIFO de l'objet conteneur components.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace ProjApplication0
{
/// <summary>
/// Description Résumé de Form1.
/// </summary>
public class WinForm : System.Windows.Forms.Form
{
/// <summary>
/// Variable requise par le concepteur.
/// </summary>
private System.ComponentModel.Container components = null;
private UnComposant MonComposant = new UnComposant( );
public WinForm ( )
{
//
// Requis pour la gestion du concepteur Windows Form
//
InitializeComponent( );
//
// TODO: Ajouter tout le code du constructeur après l'appel de InitializeComponent
//
if ( components == null )
components = new Container( );
components.Add ( MonComposant );
}
/// <summary>
/// Nettoyage des ressources utilisées.
/// </summary>
protected override void Dispose (bool disposing)
{
if (disposing) {
if (components != null) {
components.Dispose( );
}
//-- libération ici d'autres ressources managées...
}
//-- libération ici de vos ressources non managées...
base.Dispose(disposing);
}
![]()
/// <summary>
/// Le point d'entrée principal de l'application.
/// </summary>
[STAThread]
static void Main( )
{
Application.Run(new WinForm ( ));
}
}
}
1.10 L'instruction using appelle Dispose( )
La documentation technique signale que deux utilisations principales du mot clé using sont possibles :
Directive using :
Crée un alias pour un espace de noms ou importe des types définis dans d'autres espaces de noms.
Ex: using System.IO ; using System.Windows.Forms ; ...
Instruction using :
Définit une portée au bout de laquelle un objet est supprimé.
C'est cette deuxième utilisation qui nous intéresse : l'instruction using
<instruction using> ::= using ( <identif. Objet> | <liste de Déclar & instanciation> ) <bloc instruction>
<bloc instruction> ::= { < suite d'instructions > }
<identif. Objet> ::= un identificateur d'un objet existant et instancié
<liste de Déclar & instanciation> ::= une liste séparée par des virgules de déclaration et initialisation d'objets semblable à la partie initialisation d'une boucle for.
Ce qui nous donne deux cas d'écriture de l'instruction using :
1° - sur un objet déjà instancié :
classeA Obj = new classeA( );
....
using ( Obj )
{
// code quelconque....
}
2° - sur un (des) objet(s) instancié(s) localement au using :
using ( classeB Obj1 = new classeB ( ), Obj2 = new classeB ( ), Obj3 = new classeB ( ) )
{
// code quelconque....
}
Le using lance la méthode Dispose :
Dans les deux cas, on utilise une instance (Obj de classeA) ou l'on crée des instances (Obj1, Obj2 et Obj3 de classeB) dans l'instruction using pour garantir que la méthode Dispose est appelée sur l'objet lorsque l'instruction using est quittée.
Les objets que l'on utilise ou que l'on crée doivent implémenter l'interface System.IDisposable. Dans les exemples précédents classeA et classeB doivent implémenter elles-même ou par héritage l'interface System.IDisposable.
Exemple
Soit un objet visuel button1de classe System.Windows.Forms.Button, c'est la classe mère Control de Button qui implémente l'interface System.IDisposable :
public class Control : IComponent, IDisposable, IParserAccessor, IDataBindingsAccessor
Soient les lignes de code suivantes où this est une fiche :
// ....
this.button1 = new System.Windows.Forms.Button ( );
using( button1) {
// code quelconque....
}
// suite du code ....
A la sortie de l'instruction using juste avant la poursuite de l'exécution de la suite du code, button1.Dispose( ) a été automatiquement appelée par le CLR (le contrôle a été détruit et les ressources utilisées ont été libérées immédiatement).
1.11 L'attribut [STAThread]
Nous terminons notre examen du code généré automatiquement par le RAD pour une application fenêtrée de base, en indiquant la signification de l'attribut (mot entre crochets [STAThread]) situé avant la méthode main :
/// <summary>
/// Le point d'entrée principal de l'application.
/// </summary>
[STAThread]
static void Main( )
{
Application.Run(new WinForm ( ));
}
Cet attribut placé ici devant la méthode main qualifie la manière dont le CLR exécutera l'application, il signifie : Single Thread Apartments.
Il s'agit d'un modèle de gestion mémoire où l'application et tous ses composants est gérée dans un seul thread ce qui évite des conflits de ressources avec d'autres threads. Le développeur n'a pas à s'assurer de la bonne gestion des éventuels conflits de ressources entre l'application et ses composants.
Si on omet cet attribut devant la méthode main, le CLR choisi automatiquement [MTAThread] Multi Thread Apartments, modèle de mémoire dans lequel l'application et ses composants sont gérés par le CLR en plusieurs thread, le développeur doit alors s'assurer de la bonne gestion des éventuels conflits de ressources entre l'application et ses composants.
Sauf nécessité d'augmentation de la fluidité du code, il faut laisser (ou mettre en mode console) l'attribut [STAThread] :
...
[STAThread]
static void Main( )
{
Application.Run(new WinForm ( ));
}