All over Stack Overflow and the internet I see that it is a good design principle to keep structs immutable. Unfortunately, I never see any implementation that actually cau
Make the fields private readonly
and pass the initial values in the constructor
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; } }
}
Starting with C#6.0 (Visual Studio 2015) You can use getter only properties
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; }
}
Note that readonly fields and getter only properties can be initialized either in the constructor or, in classes, also with field or property initializers public int Field1 { get; } = 7;
.
It is not guaranteed that the constructor is run on a struct. E.g. if you have an array of structs, you must then call the initializers explicitly for each array element. For arrays of reference types all the elements are first initialized to null
, which makes it obvious that you have to call new
on each element. But it is easy to forget it for value types like structs.
var immutables = new ImmutableStruct[10];
immutables[0] = new ImmutableStruct(5, "hello", new Person());
immutables[1] = new ImmutableStruct(6, "world", new Student());
...
Starting with C# 7.2, you can use Read-only structs
Starting with C# 9.0 there is yet another option: the Init-Only Properties. Read-only fields and get-only auto implemented properties can be initialized in a constructor and in the field or property initializer but not in an object initializer.
This is the motivation for introducing init-only properties. They replace the set
accessor by an init
accessor. This extends the mutation phase from the actual object creation to the entire object construction phase including object initializers and with expressions (also a new C# 9.0 feature).
public string Name { get; init; }
Usage:
var x = new ImmutableStruct { Name = "John" };
x.Name = "Sue"; // Compiler error CS8852: Init-only property or indexer
// 'ImmutableStruct.Name' can only be assigned in an object initializer, or
// on 'this' or 'base' in an instance constructor or an 'init' accessor.
Keep your immutable data private:
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;
}
Or less cluttered:
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; }
}