Federico
Federico

Reputation: 330

Value semantics in c# struct vs tuple

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?

Upvotes: 3

Views: 3193

Answers (3)

Matthew Watson
Matthew Watson

Reputation: 109567

Your best bet is to do it properly and put in all the work. If you want a struct rather than a class (which may be appropriate for you), here's a sample implementation:

public struct TilePosition: IEquatable<TilePosition>
{
    public TilePosition(int horizontalPosition, int verticalPosition)
    {
        _x = horizontalPosition;
        _y = verticalPosition;
    }

    public int HComponent
    {
        get
        {
            return _x;
        }
    }

    public int VComponent
    {
        get
        {
            return _y;
        }
    }

    public static bool operator == (TilePosition lhs, TilePosition rhs)
    {
        return lhs.Equals(rhs);
    }

    public static bool operator != (TilePosition lhs, TilePosition rhs)
    {
        return !lhs.Equals(rhs);
    }

    public bool Equals(TilePosition other)
    {
        return (_x == other._x) && (_y == other._y);
    }

    public override bool Equals(object obj)
    {
        return obj is TilePosition && Equals((TilePosition)obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return (_x*397) ^ _y;
        }
    }

    private readonly int _x;
    private readonly int _y;
}

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500515

(As an aside, it would be good to implement IEquatable<TilePosition> in both cases too - particularly in the struct case, to avoid boxing.)

So are there any benefits from using a struc over a subclassed Tuple aside from not having the noise of the Item properties?

Given that it's immutable, in both cases you have roughly "value semantics" in both cases, but there are still differences...

  • An instance of the class type requires space on the heap (assuming no escape detection etc by the CLR); a value of the struct type may in some cases only use the stack
  • Passing a value of the class type just means passing a reference (4 bytes or 8 bytes depending on CLR architecture); passing a value of the struct type really passes the values (so 8 bytes)
  • In the class type version null is a valid value; in the struct type version you'd need to use TilePosition? to indicate a possibly-absent value
  • In the struct version new 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)
  • As you haven't sealed your class, someone could create a mutable subclass; it's therefore not safe for clients to assume it's fully immutable. (You should probably seal it...)
  • You can use your class type with any code which uses Tuple<,>, whereas that's clearly not the case for the struct type
  • The meaning of == 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.

Upvotes: 8

MrFox
MrFox

Reputation: 5116

How about using a jagged array and holding an item on each field. This would more closely follow the tile puzzle:

  • Tiles and space for them is a 1:1 mapping. So each tile/space can only have one space/tile.
  • No need to compare tile components.
  • Moving tiles is easy, example

        if (fields[x, y] = null)
        {
            fields[x, y] = fields[oldX, oldY];
            fields[oldX, oldY] = null;
        }
    

Upvotes: 0

Related Questions