C’est quoi les Data Structures en C# ?

Les Data Structures en C# ça ne vous dit rien ? Et si pourtant vous avez probablement déjà renconté le mot clé Struct quelque part non ?

Les Data Structures sont un type de données de type valeur, à l’inverse des classes qui sont de type référence. Les Struct sont comme un entier ou un décimal, à la différence qu’elles peuvent contenir plusieurs informations. Les variables créées à partir de Struct seront donc stockées dans la pile (stack).

Exemple

On va créer une Data Structure nommée PointStruct et une classe nommée PointClasse. Ces deux définitions stockeront les mêmes infos et pourraient dans un programme être utilisé pour assurer les mêmes objectifs : stocker et traiter des données.

Tout d’abord sous la forme d’un type data structure :

    ///
    /// Type Struct
    ///
    public struct PointStruct
    {
        public double X;
        public double Y;

        public override string ToString()
        {
            return string.Format("X: {0}, Y: {1}", this.X, this.Y);
        }
    }

Et ensuite sous la forme d’un type classe.

    ///
/// Type Classe
    ///
    public class PointClasse
    {
        public double X;
        public double Y;

        public override string ToString()
        {
            return string.Format("X: {0}, Y: {1}", this.X, this.Y);
        }
    }

Comme on peut le voir les Data Structures peuvent  contenir des méthodes tout comme les classes.

Maintenant testons nos types avec le code ci dessous :

public class BlogStructureLauncher
    {
        public void LaunchTest()
        {
            // Création d'un point de type Struct
            PointStruct pointStruct1 = new PointStruct();
            pointStruct1.X = 3.4;
            pointStruct1.Y = 4;

            // Création d'un point de type Class
            PointClasse pointClasse1 = new PointClasse();
            pointClasse1.X = 3.4;
            pointClasse1.Y = 4;

            // Affichage des coordonnées
            Console.WriteLine("Point Struct : {0}", pointStruct1.ToString());
            Console.WriteLine("Point Classe : {0}", pointClasse1.ToString());

            // Création d'un autre point de type Struct
            PointStruct pointStruct2 = pointStruct1;
            pointStruct2.X = 90;

            // Affichage des coordonnées des deux points de type Struct
            Console.WriteLine("Point Struct 1 : {0}", pointStruct1.ToString());
            Console.WriteLine("Point Struct 2 : {0}", pointStruct2.ToString());

            // Création d'un autre point de type Class
            PointClasse pointClasse2 = pointClasse1;
            pointClasse2.X = 90;

            // Affichage des coordonnées des deux points de type Class
            Console.WriteLine("Point Classe 1 : {0}", pointClasse1.ToString());
            Console.WriteLine("Point Classe 2 : {0}", pointClasse2.ToString());

        }
    }

Voici le résultat que l’on obtient dans la console :

 Point Struct : X: 3,4, Y: 4
 Point Classe : X: 3,4, Y: 4
 Point Struct 1 : X: 3,4, Y: 4
 Point Struct 2 : X: 90, Y: 4
 Point Classe 1 : X: 90, Y: 4
 Point Classe 2 : X: 90, Y: 4

Les deux premières lignes sont tout à fait normales. Les autres lignes  nous démontrent la différence fondamentale entre les types valeurs et les types références. En effet, nous avons affecté les valeurs de pointStruct1 à pointStruct2 puis modifié la propriété X de pointStruct2. Comme on peut le remarquer les valeurs de pointStruct1 sont restées les mêmes, car nous traitons de valeurs directement et non de référence à ces valeurs. Quand on affecte pointStruct1 à pointStruct2 ce sont ses valeurs qui sont copiées, non la référence à l’objet pointStruct1. Ainsi pointStruct1 et pointStruct2 sont totalement indépendant.

Pour pointClasse1 et pointClasse2 on voit bien qu’en modifiant la propriété X de l’un celà modifie la propriété X de l’autre car les deux variables pointent en réalité vers le même objet en mémoire.

Attention donc lors du passage d’un type Struct à une méthode, à moins de spécifier un passage par référence ce ne sont que les valeurs qui seront transmises à la méthode et la modification de ces valeurs à l’intérieur de la méthode ne modifiera pas les valeurs de la variable passée en paramètre.

Règles pour les struct

Il existe des points communs entre les Struct et les Class mais également pas mal de différences qui sont à connaître.

  • Le constructeur d’une Struct est optionnel mais s’il existe il doit être paramétré. En résumé il ne peut pas y avoir de constructeur par défaut définit sur une Struct. Ceci est donc interdit et empêchera la compilation :
            public PointStruct()
            {
            }
    
  • L’affectation des propriétés d’une Struct ne peut être faîte au niveau de leur déclaration. Le code suivant est donc interdit :
             public struct PointStruct
        {
            // Interdit !
            public double X = 8;
            public double Y;
    
            public PointStruct(double x, double y)
            {
                this.X = x;
                this.Y = y;
            }
    
            public override string ToString()
            {
                return string.Format("X: {0}, Y: {1}", this.X, this.Y);
            }
        }
    
  • Une Struct ne peut pas hériter d’une autre classe ou struct. En revanche elle peut implémenter une interface.
  • Il est possible de créer une Struct sans faire appel à l’opérateur new. Effectivement quand on déclare une variable de type int il est rare d’appeler new int().Le code suivant est donc parfaitement valable :
    PointStruct point1;
    point1.X = 3;
    point1.Y = 3;
    

Quand utiliser les Struct ?

A titre personnel je n’utilise que très peu les Structs, voir quasiment jamais. Il est recommandé de garder les Structs petites et simples (moins de 16 bytes).

Les Structs, du fait de leur système de stockage dans la pile, sont généralement plus performantes, mais si l’on traite de grands tableaux composés de variables de type Structs alors les performances en seront dégradées du fait de la consommation mémoire.