web-dev-qa-db-fra.com

Comment rendre une structure immuable?

Partout dans Stack Overflow et sur Internet, je vois que c'est un bon principe de conception de garder les structures immuables. Malheureusement, je ne vois aucune implémentation qui rende ces structures vraiment immuables.

En supposant qu'une structure ne contient aucun type de référence, comment puis-je réellement rendre une structure immuable? Autrement dit, comment puis-je empêcher la mutation de l'un de ses champs primitifs (peut-être par une exception de compilation/d'exécution)?

J'ai écrit un test simple essayant de rendre une structure immuable, mais sans même utiliser le System.ComponentModel.ImmutableObjectAttribute travaillé:

class Program
{
    static void Main(string[] args)
    {
        ImmutableStruct immStruct1 = new ImmutableStruct();
        Console.WriteLine(immStruct1); //Before mutation.

        immStruct1.field1 = 1;
        immStruct1.field2 = "Hello";
        immStruct1.field3 = new object();
        Console.WriteLine(immStruct1); //After 1st mutation.

        immStruct1.field1 = 2;
        immStruct1.field2 = "World";
        immStruct1.field3 = new object();
        Console.WriteLine(immStruct1); //After 2nd mutation.

        Console.ReadKey();
    }
}

[ImmutableObject(true)]
struct ImmutableStruct
{
    public int field1;
    public string field2;
    public object field3;

    public override string ToString()
    {
        string field3String = "null";
        if (field3 != null)
        {
            field3String = field3.GetHashCode().ToString();
        }
        return String.Format("Field1: {0}, Field2: {1}, Field3: {2}", field1, field2, field3String);
    }
}
22
Nicholas Miller

Faites les champs private readonly et passer les valeurs initiales dans le constructeur

public struct ImmutableStruct
{
    private readonly int _field1;
    private readonly string _field2;
    private readonly object _field3;

    public ImmutableStruct(int f1, string f2, object f3)
    {
        _field1 = f1;
        _field2 = f2;
        _field3 = f3;
    }

    public int Field1 { get { return _field1; } }
    public string Field2 { get { return _field2; } }
    public object Field3 { get { return _field3; } }
}

À partir de C # 6.0 (Visual Studio 2015) Vous pouvez utiliser uniquement des propriétés getter

public struct ImmutableStruct
{
    public ImmutableStruct(int f1, string f2, object f3)
    {
        Field1 = f1;
        Field2 = f2;
        Field3 = f3;
    }

    public int Field1 { get; }
    public string Field2 { get; }
    public object Field3 { get; }
}

Notez que les champs en lecture seule et les propriétés getter uniquement peuvent être initialisés dans le constructeur ou, dans les classes, également avec des initialiseurs de champ ou de propriété public int Field1 { get; } = 7;.

Il n'est pas garanti que le constructeur s'exécute sur une structure. Par exemple. si vous avez un tableau de structures, vous devez alors appeler explicitement les initialiseurs pour chaque élément du tableau. Pour les tableaux de types de référence, tous les éléments sont d'abord initialisés à null, ce qui rend évident que vous devez appeler new sur chaque élément. Mais il est facile de l'oublier pour les types de valeur comme les structures.

var immutables = new ImmutableStruct[10];
immutables[0] = new ImmutableStruct(5, "hello", new Person());
immutables[1] = new ImmutableStruct(6, "world", new Student());
...

À partir de C # 7.2, vous pouvez utiliser Structures en lecture seule

22

Gardez vos données immuables privées:

struct ImmutableStruct
{
    private int field1;
    private string field2;
    private object field3;

    public ImmutableStruct(int f1, string f2, object f3)
    {
        field1 = f1;
        field2 = f2;
        field3 = f3;
    }

    public int Field1 => field1;
    public string Field2 => field2;
    public object Field3 => field3;
}

Ou moins encombré:

struct ImmutableStruct
{
    public ImmutableStruct(int f1, string f2, object f3)
    {
        Field1 = f1;
        Field2 = f2;
        Field3 = f3;
    }

    public int Field1 { get; }
    public string Field2 { get; }
    public object Field3 { get; }
}
3
Quality Catalyst