c#struct vs tuple中的值语义

时间:2013-01-24 13:08:45

标签: c#

所以我在C#中迈出了第一步,正在制作一个简单的瓷砖拼图。当我模拟瓷砖的位置时,我想要有价值语义。因此,据我所知,基本上有两种方法可以实现这一点,包括结构或元组。

在元组的情况下,我的代码如下所示:

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)
    {
    }
}

struct解决方案如下所示:

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);
    }
}

元组是简洁的,但它暴露了Item1Item2,这会在公共API中引起混淆,即使我在它们周围添加了H和V组件属性。

结构需要更多的代码,我得到一个编译器警告,我应该如何重写Equals和GetHashCode,因为我正在覆盖==!=,但如果我这样做,我什么都没得到从使用结构(从语义和句法的角度来看),因为它与传统类完全相同。

除了没有Item属性的噪声之外,在子类化元组上使用struc还有什么好处吗?

我的解决方案是否都会以我预期的方式运行,或者我应该注意哪些细微差别?

3 个答案:

答案 0 :(得分:8)

(顺便说一句,在两种情况下都可以实现IEquatable<TilePosition> - 特别是在结构案例中,以避免装箱。)

  

除了没有Item属性的噪声之外,在子类化元组上使用struc还有什么好处吗?

鉴于它是不可变的,在这两种情况下你都有大致“价值语义”,但仍有差异...

  • 类类型的实例需要堆上的空间(假设CLR没有逃逸检测等);结构类型的值可能在某些情况下仅使用堆栈
  • 传递类类型的值只意味着传递一个引用(4个字节或8个字节,具体取决于CLR架构);传递struct类型的值确实传递了值(所以8个字节)
  • 在类类型中,null是一个有效值;在结构类型版本中,您需要使用TilePosition?来指示可能缺少的值
  • 在结构版本new TilePosition()中有效且两个字段的值都为0(这将是默认值,例如字段和数组元素)
  • 由于你没有密封你的课程,有人可以创建一个可变的子类;因此,客户认为它完全不可变是 安全的。 (你应该密封它......)
  • 您可以将您的类类型与任何使用Tuple<,>的代码一起使用,而结构类型显然不是这种情况
  • ==的含义在两种类型之间会有所不同。即使您在类类型中重载==,调用者仍然可能只是比较引用。在结构案例中,您仍然可能最终无法比较盒装引用。

这些只是差异当然 - 对于一种方法,它们是否算作好处,另一种取决于您的要求。

答案 1 :(得分:0)

如何使用锯齿状数组并在每个字段上保存项目。这将更紧密地遵循拼图:

  • 他们的瓷砖和空间是1:1的映射。因此每个图块/空间只能有一个空格/图块。
  • 无需比较磁贴组件。
  • 移动瓷砖很简单,例如

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

答案 2 :(得分:0)

你最好的选择是妥善完成并投入所有工作。如果你想要一个结构而不是一个类(可能适合你),这里有一个示例实现:

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;
}