You can use structure types to design data-centric types that provide value equality and little or no behavior. But for relatively large data models, structure types have some disadvantages:
- They don't support inheritance.
- They're less efficient at determining value equality. For value types, the
ValueType.Equals
method…
Top comments (1)
If you're writing a struct, you should not be relying on the default
ValueType.Equals
, because as you mention the unneeded reflection. You should always implementIEquatable<T>
and override theEquals(object)
method as well as the equality operators for every custom struct you implement.I have three rules for deciding if something should be a struct:
If it meets those three rules, then I consider it a candidate for being a struct. If it doesn't meet all three, then it should be a record or other type of class.
Not really one of my rules, but if inheritance is a concern, then, after double and triple checking the design reason for needing inheritance instead of using interfaces, I know for sure that it definitely shouldn't be a struct because it doesn't represent a value.
I don't worry about memory size of the struct. If a struct properly represents a logical value, then worrying about its memory size before you've profiled actual memory usage is a prime example of premature optimization.
Besides the normal logical value struct, there is also the wrapper struct. That is a struct that wraps an object (be it value type or reference type) for the sole purpose of doing work on that object. The most recent example that pops to mind was in a proposal I was reading the other day and involved using a
ref
readonly
struct to wrap an array reference just to have a type that extension methods could be hung on to operate on the wrapped referenced array.I'm sure many have done the same kind of thing with classes, but using a struct as the wrapper removes the overhead of instancing a class that would need to be garbage collected. In the case of the mentioned proposal, it helped with zero-allocation string creation, a place where when allocation count matters it really matters.
In conclusion, of course one should always consider the pros and cons of every design decision, but I strongly believe that the structs of modern languages carry a lot of cargo cult baggage from the bygone days of needing to count the bytes of your field types and are much more useful than a lot of programmers allow.