A storage location (variable, parameter, array element, or field) of a struct type holds the values of all its public and private fields, typically concatenated but possibly with some padding. A storage location of class type holds a (possibly null) reference to a heap object (4-8 bytes). A heap object holds 8-16 bytes of overhead plus the contents of all of the public, protected, and private fields held by the object and its ancestors.
If the things you are storing are fixed-size bundles of values, you should most likely use a struct with exposed fields. The notion that structs should be immutable goes back to days when a C# compiler would take a piece of code like:
readonly Point Pt = new Point(3,4);
void Yippie() { Pt.X += 5; }
and have Yippie
construct a new temporary Point
, copy Pt
to it, call the X
property setter on that temporary instance, and then discard it. Someone figured that the proper way to prevent such nonsense was not to have compilers say "Sorry--you can't call a property setter on a read-only struct variable", but instead to define structures so as to have read-only properties without setters. The proper thing to have done would have been to require that any struct methods or properties which were going to mutate this
indicate so in their declaration and forbid the use of such methods or properties on read-only structs.
Wrapping struct fields in properties will impair performance, and I would recommend against it except in cases where one needs to enforce struct invariants. I would also recommend that one avoid declaring structs readonly
, since any structures so declared will be copied in full any time any field is accessed.
Incidentally, an important thing to beware of with mutable class types: the state of a mutable class object not only includes the contents of its fields, but also the set of all references that exists to it. In some cases, this can be useful. Often, the only way to prevent major headaches is to have entities which hold references to mutable objects refrain from sharing such references. If making something a class type would require extra work to prevent references from being passed around promiscuously, that's a good sign that the type in question should be a struct.