Apprenez à programmer en VB .NET

Apprenez à programmer en VB .NET

Mis à jour le lundi 24 juin 2013

Attaquons maintenant la sauvegarde d'objets. Vous avez vu comment créer vos objets, vous avez aussi vu comment sauvegarder des données dans des fichiers. Mais on ne peut pas simplement écrire un objet dans un fichier comme s'il s'agissait d'une simple chaîne de caractères, il faut passer par une méthode particulière.

Mesdames et Messieurs les Zéros, je vous présente la sérialisation.

La sérialisation, c'est quoi ?

Vous vous souvenez du cycle de vie d'une variable normale, dès que la boucle, fonction, classe dans laquelle elle a été créée est terminée, la variable est détruite et libérée de la mémoire. Les données qu'elle contient sont donc perdues.

Vous avez dû vous douter que le même principe s'appliquait pour les objets. Et là ce n'est pas une simple valeur de variable qui est perdue, mais toutes les variables que l'objet contenait (des attributs). Pour sauvegarder notre variable pour une utilisation ultérieure (un autre lancement du programme), nous avons vu comment stocker cette variable dans un fichier.

Les Integer, String, Date, bref les variables basiques, sont facilement stockables, mais les objets le sont plus difficilement.

Je vais créer une classe basique contenant des informations concrètes. Le projet que j'ai créé est de type console, car l'interface graphique est superflue pour le moment.

Public Class Film

    Public Titre As String
    Public Annee As Integer
    Public Desciption As String

    Sub New()

    End Sub

    Sub New(ByVal TitreFilm As String, ByVal AnneeFilm As Integer, ByVal DescriptionFilm As String)
        Titre = TitreFilm
        Annee = AnneeFilm
        Desciption = DescriptionFilm
    End Sub

End Class

Ma classe s'appelle Film, elle va contenir des informations pour créer un objet Film auquel on spécifiera le nom, l'année de sortie et la description.

Cette classe est très basique, seulement trois attributs. Mais si je veux la sauvegarder, il va déjà falloir écrire trois variables différentes dans un fichier. Donc imaginez avec plusieurs dizaines d'informations (attributs).

Avec la sérialisation (le stockage d'objet) on va pouvoir facilement écrire tous les attributs d'un objet dans un fichier (voir figure suivante). Ce fichier sera automatiquement formaté. Ce formatage va dépendre de la méthode de sérialisation utilisée.

La sérialisation permet d'écrire tous les attributs d'un objet dans un fichier
La sérialisation permet d'écrire tous les attributs d'un objet dans un fichier

Il existe donc deux méthodes.

La méthode binaire permet la sauvegarde des attributs publics et privés tout en incluant également le nom de la classe dans le fichier.
La seconde méthode, la sérialisation XML, est très utile car le format SOAP (le type de formatage d'un fichier XML) est très répandu, pour communiquer via des webservices et en général sur l'internet. Ses inconvénients sont que les attributs privés ne sont pas pris en compte et les types des attributs ne sont enregistrés nulle part.

Commençons avec la sérialisation binaire.

La sérialisation binaire

Pour commencer je modifie notre classe Film pour spécifier au programme qu'il peut la sérialiser grâce à <Serializable()> inscrit dans sa déclaration :

<Serializable()>
Public Class Film

    Public Titre As String
    Public Annee As Integer
    Public Desciption As String

    Sub New()

    End Sub

    Sub New(ByVal TitreFilm As String, ByVal AnneeFilm As Integer, ByVal DescriptionFilm As String)
        Titre = TitreFilm
        Annee = AnneeFilm
        Desciption = DescriptionFilm
    End Sub

End Class

Ensuite, dans la page contenant le code qui va permettre de sérialiser l'objet, on va faire deux imports, l'un permettant d'utiliser la sérialisation, le second permettant l'écriture dans un fichier :

Imports System.Runtime.Serialization.Formatters.binary
Imports System.IO

Je ne sais pas si vous vous souvenez de la partie 1 sur les fichiers, mais elle était fastidieuse, car je vous faisais travailler sur des flux. La sérialisation reprend le même principe, nous allons ouvrir un flux d'écriture sur le fichier et la fonction de sérialisation va se charger de le remplir.

Donc pour remplir un fichier bin avec un objet que je viens de créer :

'On crée l'objet
            Dim Avatar As New Film("Avatar", 2009, "Avatar, film de James Cameron sorti en décembre 2009.")
            'On crée le fichier et récupère son flux
            Dim FluxDeFichier As FileStream = File.Create("Film.bin")
            Dim Serialiseur As New BinaryFormatter
            'Sérialisation et écriture
            Serialiseur.Serialize(FluxDeFichier, Avatar)
            'Fermeture du fichier
            FluxDeFichier.Close()

Et pourquoi .bin, l'extension du fichier ?

Ce n'est pas obligatoire de lui assigner cette extension, c'est juste une convention. Comme lorsque nous créions des fichiers de configuration, nous avions tendance à les nommer en .ini, même si nous aurions pu leur donner l'extension .bla.

Le BinaryFormatter est un objet qui va se charger de la sérialisation binaire. C'est lui qui va effectuer le formatage spécifique à ce type de sérialisation.

L'objet Avatar étant un film, il est désormais sauvegardé ; si je veux récupérer les informations, il faut que je le désérialise.

La désérialisation est l'opposé de la sérialisation. Sur le principe de la lecture/écriture dans un fichier. La sérialisation écrit l'objet, la désérialisation le lit. Nous allons utiliser le même BinaryFormatter que lors de la sérialisation.

Programmatiquement :

If File.Exists("Film.bin") Then
            'Je crée ma classe « vide »
            Dim Avatar As New Film()
            'On ouvre le fichier et récupère son flux
            Dim FluxDeFichier As Stream = File.OpenRead("Film.bin")
            Dim Deserialiseur As New BinaryFormatter()
            'Désérialisation et conversion de ce qu'on récupère dans le type « Film »
            Avatar = CType(Deserialiseur.Deserialize(FluxDeFichier), Film)
            'Fermeture du flux
            FluxDeFichier.Close()
        End If

Je vérifie avant tout que le fichier est bien présent. La ligne de désérialisation effectue deux opérations : la désérialisation et la conversion avec Ctype de ce qu'on récupère dans le type de notre classe (ici Film). Et on entre le tout dans Avatar. Si vous analysez les attributs, vous remarquez que notre film a bien été récupéré.

VB .Net est « intelligent », si vous n'aviez pas effectué de Ctype, il aurait tout de même réussi à l'insérer dans l'objet Avatar. La seule différence est que le Ctype offre une meilleure vue et compréhension de notre programme. La même remarque peut être faite lors de renvois de valeurs via des fonctions, le type de retour n'est pas forcément spécifié, une variable de type Object (spécifique à Visual Basic) sera alors retournée, mais pour un programmeur un code avec des « oublis » de ce type peut très vite devenir incompréhensible et ouvre la porte à beaucoup de possibilités d'erreurs.

Bon, je ne vous montre pas le contenu du fichier résultant, ce n'est pas beau à voir. Ce n'est pas fait pour ça en même temps. La sérialisation XML, quant à elle, va nous donner un résultat plus compréhensible et modifiable manuellement par un simple mortel.

Voyons ça tout de suite.

La sérialisation XML

La sérialisation XML, quant à elle, respecte le protocole SOAP (Simple Object Access Protocol), le fichier XML que vous allez générer va pouvoir être transporté et utilisé plus facilement sur d'autres plateformes et langages de programmation qui respecteront eux aussi le protocole SOAP.

Le format XML (je vous renvoie à sa page Wikipédia pour plus d'informations) formate le fichier avec une stucture bien spécifique composée de sections et sous-sections.

Je reprends l'exemple de Wikipédia pour vous détailler rapidement sa composition :

<?xml version="1.0" encoding="UTF-8"?>
  <!-- '''Commentaire''' -->
  <élément-document xmlns="http://exemple.org/" xml:lang=";fr">
    <élément>Texte</élément>
    <élément>élément répété</élément>
    <élément>
      <élément>Hiérarchie récursive</élément>
    </élément>
    <élément>Texte avec<élément>un élément</élément>inclus</élément>
    <élément/><!-- élément vide -->
    <élément attribut="valeur"></élément>
  </élément-document>

La première ligne spécifiant une instruction de traitement, elle est nécessaire pour des logiciels traitant ce type de fichier, une convention encore une fois.
Vient une ligne de commentaire, elle n'est pas censée nous intéresser, car notre sérialisation ne génèrera pas de commentaire.
En revanche, le reste est intéressant. La sérialisation va convertir la classe en un nœud principal (élément racine) et ses attributs seront transformés en sous-éléments.

Un exemple simple du résultat de la sérialisation :

Public Class ClasseExemple
    Public ValeurNumerique As Integer
End Class
<ClasseExemple>
    <ValeurNumerique>23</ValeurNumerique>
</ClasseExemple>

Vous arrivez à distinguer à l'œil nu les corrélations qui sont présentes entre la classe et sa sérialisation, ce qui va être beaucoup plus facile pour les modifications manuelles.

Bon, passons à la programmation.

On va changer un import que nous utilisions pour la sérialisation binaire.

Ce qui nous amène à écrire :

Imports System.IO
Imports System.Xml.Serialization

… où System.IO contient toujours de quoi interagir avec les fichiers, et System.Xml.Serialization, les classes nécessaires à la sérialisation XML.

Et dans notre code, presque aucun changement :

Dim Avatar As New Film("Avatar", 2009, "Avatar, film de James Cameron sorti en décembre 2009.")
'On crée le fichier et récupère son flux
Dim FluxDeFichier As FileStream = File.Create("Film.xml")
Dim Serialiseur As New XmlSerializer(GetType(Film))
'Sérialisation et écriture
Serialiseur.Serialize(FluxDeFichier, Avatar)
'Fermeture du fichier
FluxDeFichier.Close()

Eh oui, le principe de sérialisation reste le même, c'est juste un objet de type XmlSerializer qu'on instancie cette fois-ci. Il faut passer un argument à son constructeur : le type de l'objet que nous allons sérialiser. Ici c'est ma classe Film, donc la fonction GetType(Film) convient parfaitement.

Une fois la sérialisation effectuée, le fichier en résultant contient :

<?xml version="1.0"?>
<Film xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Titre>Avatar</Titre>
  <Annee>2009</Annee>
  <Desciption>Avatar, film de James Cameron sorti en décembre 2009.</Desciption>
</Film>

Un beau fichier bien formaté !

Pour la désérialisation, même principe :

If File.Exists("Film.xml") Then
    'Je crée ma classe « vide »
    Dim Avatar As New Film()
    'On ouvre le fichier et récupère son flux
    Dim FluxDeFichier As Stream = File.OpenRead("Film.xml")
    Dim Deserialiseur As New XmlSerializer(GetType(Film))
    'Désérialisation et conversion de ce qu'on récupère dans le type « Film »
    Avatar = CType(Deserialiseur.Deserialize(FluxDeFichier), Film)
    'Fermeture du flux
    FluxDeFichier.Close()
End If

Juste un petit mot sur la taille du fichier résultant avec les deux méthodes, binaire et XML.

Même si le fichier XML semble plus long, leur taille ne diffère que du type de formatage qu'utilise XML.

Si votre objet contient 1ko de données programmatiquement parlant, la sérialisation n'est pas là pour réduire cette taille. Au contraire, le formatage qu'apporte la sérialisation (binaire ou XML) ne tend qu'à l'alourdir.

La sérialisation multiple

Bon, jusqu'à présent aucun problème pour ce qui est de sérialiser notre petit film « Avatar ». Mais imaginez que nous voulions enregistrer toute une bibliothèque de films (une idée de TP ? :) ).

Déjà, avant que vous vous enfonciez, je vous dis qu'avec notre méthode ça ne va pas être possible.

Et pourquoi ça ?

Eh bien vous avez vu que le nœud principal de notre document XML est <Film>, et j'ai dit qu'il ne pouvait y avoir qu'un seul nœud principal par document XML, autrement dit, qu'un seul film.
Certes, il reste l'idée de créer un fichier XML par film à sauvegarder, mais je pense que ce n'est pas la meilleure méthode. :)

Rappelez-vous nos amis les tableaux. Un tableau de String, vous vous en souvenez, et dans un TP je vous ai tendu un piège en faisant un tableau de RadioButton.

Ça va être le même principe ici, un tableau de Film.

Oui, il fallait juste y penser. Ce qui nous donne, en création d'un tableau de Film et sérialisation du tout :

Dim Films(1) As Film
Films(0) = New Film("Avatar", 2009, "Avatar, film de James Cameron sorti en décembre 2009.")
Films(1) = New Film("Twilight 3", 2010, "Troisième volet de la quadrilogie Twilight")
'On crée le fichier et récupère son flux
Dim FluxDeFichier As FileStream = File.Create("Films.xml")
Dim Serialiseur As New XmlSerializer(GetType(Film()))
'Sérialisation et écriture
Serialiseur.Serialize(FluxDeFichier, Films)
'Fermeture du fichier
FluxDeFichier.Close()

Pour la création du tableau, je ne vous fais pas d'explication je pense. Un tableau de deux cases, Avatar dans la première, Twilight 3 dans la seconde (il va falloir que je mette à jour ce tuto dans le futur, avec les films du moment :p ).

La seule particularité est le type que l'on fournit au XmlSerializer. Ce n'est pas un simple GetType(Film), autrement dit le type de ma classe Film, mais c'est GetType(Film()), le type d'un tableau de ma classe Film.

Une fois la sérialisation effectuée, notre fichier se compose ainsi :

<?xml version="1.0"?>
<ArrayOfFilm xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Film>
    <Titre>Avatar</Titre>
    <Annee>2009</Annee>
    <Desciption>Avatar, film de James Cameron sorti en décembre 2009.</Desciption>
  </Film>
  <Film>
    <Titre>Twilight 3</Titre>
    <Annee>2010</Annee>
    <Desciption>Troisième volet de la quadrilogie Twilight</Desciption>
  </Film>
</ArrayOfFilm>

Le nœud principal est désormais un ArrayOfFilm, soit un tableau de Film(). Et chaque nœud secondaire correspond à un film.

La désérialisation est pratiquement la même elle aussi :

If File.Exists("Film.xml") Then
   'On ouvre le fichier et récupère son flux
   Dim FluxDeFichier As Stream = File.OpenRead("Film.xml")
   Dim Deserialiseur As New XmlSerializer(GetType(Film()))
   'Désérialisation et insertion dans le tableau de Film()
   Dim Films() As Film = Deserialiseur.Deserialize(FluxDeFichier)
   'Fermeture du flux
   FluxDeFichier.Close()
End If

Ici, même remarque que pour la sérialisation, le type qu'on fournit est bien un tableau de Film() et non plus un Film simple.
Seconde remarque : j'ai effectué la déclaration de mon tableau sur la même ligne que la désérialisation. Cela me permet de m'affranchir de la déclaration fixe de la longueur de mon tableau.
Autrement dit, le programme va se charger tout seul de déterminer combien il y a de films présents et construira un tableau de la taille requise. J'ai fait cela en écrivant Films() au lieu de Films(1). Mais le résultat sera le même, et au moins pas de risque d'erreurs de taille. :D

Et en ce qui concerne les collections, que nous venons d'aborder, la méthode est la même :

Imports System.IO
Imports System.Xml.Serialization

Module Module1

    Sub Main()

        Dim MaListeDeClasses As New List(Of Classe)
        MaListeDeClasses.Add(New Classe("Avatar"))
        MaListeDeClasses.Add(New Classe("Twilight 1"))
        MaListeDeClasses.Insert(0, New Classe("Titanic"))

        'On crée le fichier et récupère son flux
        Dim FluxDeFichier As FileStream = File.Create("C:\Classes.xml")
        Dim Serialiseur As New XmlSerializer(GetType(List(Of Classe)))
        'Sérialisation et écriture
        Serialiseur.Serialize(FluxDeFichier, MaListeDeClasses)
        'Fermeture du fichier
        FluxDeFichier.Close()

    End Sub

End Module

Public Class Classe

    Private _Variable As String

    Sub New()

    End Sub

    Sub New(ByVal Variable As String)
        _Variable = Variable
    End Sub

    Public Property Variable As String
        Get
            Return _Variable
        End Get
        Set(ByVal value As String)
            _Variable = value
        End Set
    End Property

End Class

Sur le même principe, le GetType s'effectue sur une List(Of Classe). Et le fichier XML résultant a le même schéma qu'un tableau :

<?xml version="1.0"?>
<ArrayOfClasse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Classe>
    <Variable>Titanic</Variable>
  </Classe>
  <Classe>
    <Variable>Avatar</Variable>
  </Classe>
  <Classe>
    <Variable>Twilight 1</Variable>
  </Classe>
</ArrayOfClasse>

Il serait même préférable à présent d'utiliser des collections, qui sont plus modulaires et qui représentent mieux les concepts de la POO que des tableaux (archaïques). :)

En conclusion, la sérialisation est vraiment très pratique, une simple ligne pour sauvegarder tout un objet.

Sur ce principe, une configuration peut aisément être sauvegardée dans un objet fait par vos soins recensant toutes les valeurs nécessaires au fonctionnement de votre programme, puis une sérialisation XML vous donnera un fichier clair et formaté, au même titre qu'un fichier .ini. Même si ce n'est pas son utilisation principale, ce peut être une bonne alternative.

Pour combler cette lacune, deux solutions : passer tout ses arguments en publics, mais cette technique « tue » le principe de la POO, ou alors utiliser des propriétés. Les property. Si votre attribut est privé et que vous avez créé la property publique correspondante, il sera sérialisé.

Le XML est donc un format générique et il a été conçu pour stocker des données. Lorsque nous aborderons le chapitre sur les bases de données, les premières notions utiliseront sûrement des documents XML, c'est une base de données comme une autre…

Bref, n'allons pas trop vite, il nous reste à finir cette méchante partie d'orienté objet avant d'attaquer ces autres concepts. ^^

  • La sérialisation permet de sauvegarder des objets.

  • On peut ensuite les sauvegarder sous forme de fichiers ou alors de flux (nous verrons cela plus tard).

  • Nous pourrons ainsi envoyer des classes entières par le réseau à un programme distant.

Exemple de certificat de réussite
Exemple de certificat de réussite