Can Structs Have Methods?
I just got this question in Discord:
I’m debating whether to make a class or a struct. I know I’m going to need a few methods for what I’m doing. If I have methods, is it okay to put them in the struct? Should I put the methods in a separate static helper class instead? Should the whole thing just be a class?
From a syntax standpoint, structs allow methods. So the answer to the (unasked) question, “Can structs have methods?” is a clear “Yes.”
Of course, the real question isn’t “Can I?” but “Should I?”
My reaction, when I saw this question, was to go look at some examples from the Base Class Library. So I went to MSDN and found some common structs to look at.
System.Guid, for example.
The first thing to note is that
Guid has methods.
Lots of them, in fact.
These fall into a few categories:
Guidwants to ensure that two identifiers with the same data are considered equal, so it overrides
GetHashCode,and also provides a definition for the
- Static Factory Methods: The static methods
NewGuidare all methods for creating a new GUID from data in another format. Arguably, static methods are in a different category than what this question was asking for, but it is useful to see that data conversion methods are extremely common for structs.
- Conversion to Other Types: The methods
TryWriteBytes, and even
ToStringare all methods intended to convert a GUID to other formats.
- Comparisons: The last method,
CompareTo, is used to order GUIDs.
This struct makes heavy use of methods, but it is interesting to note the categories they fall in. These seem to land in two buckets:
- Converting between different data representations (the factory and conversion methods).
- Answering basic questions about the data (equality and comparisons).
Let’s look at another one at random:
Range is used by the compiler to support range-based access to a collection, such as
It is composed of two
System.Index values, each of which is a pairing of an
bool, where the
int represents the raw number and the
bool represents if the index is from the front or back of the array.
Range’s methods seem to fit the same pattern as
- Data Access: Technically, the
Endproperties are methods that retrieve the raw data from in the struct. In fairness, while these are methods, they are also meant to look like field access. So you might (reasonably) count these as data, not methods with behavior.
- Static Factory Methods:
StartAtboth create new
Rangevalues that only have one end (contrasted with the construtor, which expects both start and end indices).
Equalsmake an appearance here as well.
- Conversion to Other Types: The
ToStringmethod gives you a text representation of the range.
- The Interesting One: The last method
GetOffsetAndLength, and is the most interesting of
GetOffsetAndLength is the method with the most “behavior” of them all. It isn’t just simple data access, nor is it about conversion.
But it still fits into Category #2 above: Answering basic questions about the data.
We could keep looking at structs in the Base Class Library, but we’ll stop here for now.
I think what we’ve seen gives us some great rules for when it is okay to put methods into a struct:
- Converting between different data representations (constructors, factory methods,
- Answering questions about the data (“Are these two things equal?”, “How do I order these?”, and even calculations that use the data.)
- Data access (usually in the form of properties).
If your methods go beyond these–if the focus is on behavior and not data–then perhaps you want a class after all.