The size of the struct is only the struct itself, not of objects it refers to that lives on the heap.
For a string, that's a reference, which adds either 32 bits (4 bytes) or 64 (8) depending on the CPU technology you're running under. The actual string is on the heap and is not relevant to the purpose of this rule.
The purpose of this rule is that when you pass structs around, they're copied, byte for byte, and when they start to become big, that copying tends to add more overhead than what you gain from avoiding putting pressure on the garbage collector.
There are numerous questions here on Stack Overflow related to how to obtain the size of a struct (or something meaningful in this regard anyway), I suggest you do a search. In short, no, it's not as easy as simply totalling the size of each individual field, as there may be padding and alignment that impacts this.
An immutable struct is one that doesn't change after given a value. The easiest (but by no means the only) way to do this is to make all the fields readonly
, meaning that once given a value through a constructor, they can't be changed (easily).
The purpose of this rule is that there are hidden gotchas related to boxing, unboxing, copying, etc. that will throw a spanner in the works if you create mutable structs.
There's numerous questions on this topic as well, search for "Mutable structs are evil".
Here's an example of "evil mutable structs". Given this code:
public struct Struct
{
public int Value;
public void Change()
{
Value++;
}
}
private Struct _Struct = new Struct();
public Struct GetStruct()
{
return _Struct;
}
public Struct StructProperty
{
get
{
return _Struct;
}
}
This will "work", it will output first 0 and then 1, the struct was changed.
void Main()
{
_Struct.Value.Dump();
_Struct.Change();
_Struct.Value.Dump();
}
This will not, it will output 0 and 0, because the copy returned from the property was changed.
void Main()
{
_Struct.Value.Dump();
StructProperty.Change();
_Struct.Value.Dump();
}
Neither will this, same result:
void Main()
{
_Struct.Value.Dump();
GetStruct().Change();
_Struct.Value.Dump();
}
Note: If your intended goal was to actually not disturb the underlying struct, then this "works", from that standpoint, but all my experience with mutable structs tells me that sooner rather than later you're moving structs around and implicitly expecting them to behave like instance references.