Conception : Immutabilité

22 mai 2026

Conception : Immutabilité

Voici un nouvel article de la section Design, et dans celui-ci, nous aborderons un concept fondamental de la programmation fonctionnelle de plus en plus présent dans l’univers orienté objet : l’immutabilité.

L’objectif est d’apporter les motivations, les avantages, et de montrer comment il est possible de l’implémenter en C#.

Allons-y !

Qu’est-ce que l’immutabilité

L’immutabilité est la caractéristique d’une instance donnée d’une structure de données d’avoir son état préservé et immuable une fois créée. Cela signifie que, lorsqu’une nouvelle valeur est nécessaire pour l’une de ses propriétés, une nouvelle copie de cette instance est créée.

Cette approche apporte certaines avantages, ainsi que quelques préoccupations, certaines pas très évidentes, et nous allons les explorer ci-après.

Multi-threading et parallélisme

La première grande avantage apportée par l’immutabilité est la préservation de l’état d’un objet lorsqu’il est accédé par plusieurs threads (thread safety) ou en parallèle (concurrence). Dans des scénarios où un objet est mutable, il est nécessaire de mettre en place un mécanisme de synchronisation (la forme la plus courante est via le mot-clé lock), ce qui finit par ajouter une complexité à l’application car le programmeur doit veiller à vérifier que tous les endroits qui accèdent à cet objet dans le but de modifier son état sont correctement synchronisés.

Lisibilité, Débogage et Tests

Une autre avantage très intéressant, bien que pas si évident, est l’augmentation de la lisibilité du code et la facilitation des tests et du débogage. Cela parce que, comme l’objet ne subit pas de modifications, il suffit de repérer les points où de nouveaux objets sont créés pour vérifier s’il y a des erreurs dans son initialisation, ce qui augmente l’efficacité puisque c’est quelque chose de plus facile à trouver que les points de modification, surtout si ces modifications n’indiquent pas le type de l’objet qui est modifié (lorsque var est utilisé, par exemple).

Performance

Étant donné que des modifications ne sont pas possibles sur un objet, et que la création d’une nouvelle instance est requise, il existe un potentiel d’utilisation de mémoire plus élevé, notamment sur le chemin critique de l’application en cas d’utilisation intensive des ressources. Dans ces cas, une conception qui prend en compte cette utilisation intensive et la nécessité d’éviter les modifications est fondamentale. Les langages fonctionnels sont optimisés pour ce scénario, avec des mécanismes tels que le partage de l’adresse mémoire des propriétés des instances, ainsi une nouvelle instance n’a pas besoin de copier les données, seulement des références, ce qui est bien plus efficace.

Immutabilité et C#

Dans ses dernières versions, l’équipe de .NET a beaucoup investi dans des ressources pour rendre le C# compatible avec l’immuabilité, et examinons les principaux.

Readonly Structs

C’était l’une des premières fonctionnalités du langage pour traiter l’immutabilité. Lorsqu’elles sont déclarées avec le mot-clé readonly, l’instance passe à un état où les modifications sont interdites. Ainsi, toute tentative de modification, y compris par des méthodes de l’instance elle-même, ne fonctionnera pas.

Voyons un exemple en code :

public readonly struct Struct
{
    int _value;

    public Struct(int value) =>
        _value = value;

    public void Add(int value) =>
        _value += value;
}

public static void Main()
{
    var @struct = new Struct(3);
    @struct.Add(7);
    Console.Write($"Value: {@struct}"); //3
}

Note : la manière dont le compilateur garantit l’immuabilité des readonly structs est via les copies défensives (defensive copies). En termes simples, chaque fois que l’instance doit être accédée, une copie de son état d’origine est créée. Ainsi, l’instance originale voit son état protégé, même si au prix d’une consommation plus élevée de la stack. Il existe des façons de résoudre ce problème, essentiellement en passant l’instance par référence en utilisant le mot-clé in dans la signature de la méthode qui la recevra, ou par l’utilisation de ref readonly lorsque l’on utilise une instance qui se trouve dans une portée supérieure.

Records

Un autre type qui apporte l’immuabilité par design est le record. Une fois initialisés, leurs valeurs ne peuvent plus être modifiées, ce qui rend possible d’avoir des classes immuables (puisque les records sont des types de référence).

Un détail à prendre en compte, toutefois, est que les records peuvent devenir mutables si leurs propriétés sont déclarées avec des setters. Autrement dit, pour tirer parti de l’immuabilité, les propriétés doivent être déclarées avec des initializers ou de manière positionnelle.

Voyons des exemples :

public record Person
{
    public string Name { get; init; }
    public byte Age { get; init; }
}

Équivalent à :

public record Person (string Name, byte Age);

Ainsi, il est garanti qu’aucun changement ne peut être effectué.

Champs Read-only

Ceci est une fonctionnalité assez connue qui, fondamentalement, indique qu’un champ donné d’un objet ne peut être lu que et devra obligatoirement être initialisé (soit par attribution au moment de la déclaration, soit via le constructeur).

Voyons comment cela fonctionne :

public class Person
{
    public readonly String _name = "Person Name";
}

Ce qui équivaut à :

public class Person
{
    public readonly String _name;

    public Person(string name) =>
        _name = name;
}

Ainsi, une fois initialisé, la valeur de name ne peut pas être modifiée.

Création de nouvelles instances

Pour faciliter la création de nouvelles instances, en affectant uniquement les nouvelles valeurs, c’est le mot-clé with. Il permet au compilateur d’attribuer à la nouvelle instance toutes les propriétés qui ne sont pas déclarées dans sa portée.

Voyons un exemple :

public record Person(string Name, byte Age);

public static void Main()
{
    var john = new ("John", 30);
    var robert = john with { Name = "Robert" };
    Console.Write($"John's age: {john.Age}, Robert's age: {robert.Age}");
    //John's age: 30, Robert's age: 30
}

Notez qu’au-dessus, john reste inchangé et robert ne voit que son nom modifié, sa valeur d’âge restant identique à celle de john.

Considérations finales

L’immutabilité est une ressource excellente pour limiter les bugs liés à la concurrence, la gestion de l’état, la simplicité, la prévisibilité et la lisibilité du code.

Ces avantages ont une valeur énorme et, simultanément, les ressources offertes par C# permettent de les exploiter facilement.

Je recommande vivement de considérer l’immutabilité comme un outil de conception en l’utilisant également comme une métrique de qualité, car si un objet donné subit de nombreuses modifications, il est probable que sa façon d’être utilisé soit inadéquate.

Fabien Delpont

Auteur

Fabien Delpont

Fabien Delpont, développeur et créateur du site Python Doctor.