So I’m making my first steps in C# and was making a simple tile puzzle. When I was modeling the position of a tile I wanted to have value semantics. So, as far as I can see there are basically two ways of doing this, with a struct or with a Tuple.
In the case of the Tuple my code looks like this:
public class TilePosition : Tuple<int,int> { public int HComponent{get { return Item1; }} public int VComponent{get { return Item2; }} public TilePosition(int horizontalPosition, int verticalPosition) : base(horizontalPosition, verticalPosition) { } }
The struct solution would look like this:
public struct TilePosition { private readonly int hComponent; private readonly int vComponent; public int HComponent { get { return hComponent; } } public int VComponent { get { return vComponent; } } public TilePosition(int hComponent, int vComponent) { this.hComponent = hComponent; this.vComponent = vComponent; } public static bool operator ==(TilePosition position1, TilePosition position2) { return position1.Equals(position2); } public static bool operator !=(TilePosition position1, TilePosition position2) { return !(position1 == position2); } }
The tuple is conciser but it exposes Item1 and Item2 which would be confusing in a public API, even though I have added the H and V component properties around them.
The struct need more code and I get a compiler warning about how I should override Equals and GetHashCode because I’m overriding == and !=, but if I do that I’m not getting anything from using a struct (from the semantic and syntactic point of view) because it wold be exactly the same code with a conventional class.
So are there any benefits from using a struc over a subclassed Tuple aside from not having the noise of the Item properties?
Would both of my solution behave in the same way as I expect or are there nuances I should be aware of?
(As an aside, it would be good to implement
IEquatable<TilePosition>in both cases too – particularly in the struct case, to avoid boxing.)Given that it’s immutable, in both cases you have roughly “value semantics” in both cases, but there are still differences…
nullis a valid value; in the struct type version you’d need to useTilePosition?to indicate a possibly-absent valuenew TilePosition()is valid and will have values of 0 for both fields (and this will be the default value, e.g. for fields and array elements)Tuple<,>, whereas that’s clearly not the case for the struct type==will differ between the two types. Even if you overload==in the class type, a caller could still end up just comparing references. And in the struct case, you could still end up comparing boxed references, unhelpfully.These are just differences of course – whether they count as benefits for one approach or the other depends on your requirements.