7

我有一个名为的小类,它有一个名为(a structTank )的公共成员。当我写:LocationRectangle

Tank t = new Tank();
t.Location.X+=10;

一切正常,坦克移动。

但是在我将成员更改为属性后,我就不能再使用这种语法了。它不能编译,因为t.Location它现在是一个属性(它是函数)并返回位置的临时副本(因为它是一个值类型)。

我现在可以使用的唯一方法Location是执行以下操作:

k = t.Location 
k.X +=10;
t.Location = k;

是否有任何解决方法可以帮助我不编写这种丑陋的代码并使用直观的a+=10;语法?

4

5 回答 5

3

来自@Servy
“结构是不可变的”,不,它们不是。在大多数情况下,它们应该是,但它们本质上不是不可变的。这里的固有问题是该属性返回结构的副本,而不是对结构的引用。如果 C# 中有用于 ref 返回的语法,那么这是可能的。

从根本上说为什么这不起作用是结构是不可变的。一旦制作完成,就是这样。因此,不可能部分地重新分配结构。这就像试图换掉你的腿。你不能。它是你的一部分,你也随之而来!

我认为你唯一能做的就是实现你自己的 X 和 Y 属性,例如:

public double LocationX
{
   get
   {
       return Location.X;
   }
   set
   {
       Location = new Rectangle(value,Location.Y);
   }
}

您显然也需要将此镜像到 Y,但这应该允许您想要什么(但不要指望它快速或高效!)

虽然这回答了您的直接问题,但我会就您的模型提出几点意见。我会考虑不要尝试像这样更新运动。从 OO 的角度来看,你的坦克是它自己的对象,应该管理它自己的位置。给它一个移动指令,然后让它更新它自己的位置。

例如:

Tank.MoveRelative(10,0);   
Tank.MoveAbsolute(100,100);

这让你有更多的自由,并允许坦克根据你给它的逻辑来验证对其提出的任何请求。

于 2013-02-25T19:49:22.440 回答
2

当您开始使用属性在 2D 和 3D 空间中编程时,经常会出现此问题。通常,最好的解决方法是在两个向量结构或两个不同的结构之间实现加法,它们将以逻辑方式加在一起(在您的情况下,您将在 2D 向量和矩形之间实现加法以偏移其位置 - 您不会将两个矩形相加)。

这样做,您可以编写:

myTank.Location += new Vector2(10, 0);

虽然仍然有些笨拙,但允许您在一次操作中对两个组件进行更改。理想情况下,添加的矢量将是一个速度矢量,您将使用它来更新您的坦克的位置。

于 2013-02-25T19:54:06.543 回答
2

我建议制定一种移动坦克的方法。

public class Tank
{
    private Rectangle _location;

    public int X { get { return _location.X; } }
    public int Y { get { return _location.Y; } }

    public Tank(int width, int height /* other params */)
    {
        _location = new Rectangle(0, 0, width, height);
    }

    public Tank Move(Point offset)
    {
        _location.X += offset.X;
        _location.Y += offset.Y;

        return this;
    }
}

用法是

var tank = new Tank(1, 1);
tank.Move(new Point(1, 1)).Move(new Point(1, 1)); //Tank would have X: 2, Y: 2

这可以更改为使用Vector2或其他。

于 2013-02-25T20:00:24.947 回答
0

核心区别在于,属性被归类为函数,而字段被归类为变量。函数成员调用开始。

一种解决方法是使用字段或后备存储,而不是像您所做的那样使用属性。应该避免创建可变值类型,因为这种行为通常令人惊讶、难以预测和/或有时完全不一致。

这里有一些细节,规范中的相关部分有助于描述您正在经历的行为。

C# 4.0 第 1.6.7.2 节

集合访问器对应于具有单个参数名为 value 且没有返回类型的方法。

get 访问器对应于具有属性类型返回值的无参数方法。

现在切换到 7.5.5函数成员调用,相关部分:

如果 [函数成员] 是在值类型中声明的实例函数成员:

如果 [实例表达式] 未被分类为变量,则创建 [实例表达式] 类型的临时局部变量,并将 [实例表达式] 的值分配给该变量。[实例表达式] 然后被重新分类为对该临时变量的引用。临时变量可以在 [函数成员] 中作为 this 访问,但不能以任何其他方式访问。 因此,只有当 [实例表达式] 是一个真正的变量时,调用者才有可能观察到 [函数成员] 对 this 所做的更改。

于 2013-02-25T20:27:54.170 回答
0

如果类或结构类型变量公开了值类型字段,并且该值类型将其内容公开为字段,则对这些字段的操作可以像对周围变量类型的操作数一样有效地执行。如果值类型作为属性公开,那么最好的方法通常是:

var temp = t.Location;
temp.X += 4;
t.Location = temp;

不是非常优雅,但相对清晰,也不是太低效。另一种方法是让 tank 公开一个方法AdjustLocation,例如:

delegate void ActByRef<T1>(ref T1 p1);
void ActOnLocation(ActByRef<Point> proc)
  { proc(ref _Location); }

可能还有

delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
void ActOnLocation<PT1>(ActByRef<Point, PT1>, ref PT1 param1)
  { proc(ref _Location, ref param1); }

这些方法假定Location属性使用名为 的支持字段_Location。然后代码可以执行以下操作:

// Add 5 to X
myTank.ActOnLocation( (ref Point loc) => loc.X += 5 ); 

或者

// Add YSpeed to Y
myTank.ActOnLocation( (ref Point loc, ref int param) => loc.Y += param, ref YSpeed);

请注意,在后一种情况下, lambda 中YSpeed既不this使用 也不使用任何其他局部变量;相反,YSpeed作为ref参数传递。因此,即使上面的代码运行一百万次,系统也只需要生成一个委托,然后每次都可以重复使用。

如果结构很大,则上述方法可能比使用临时变量的方法更快。虽然开销可能大于复制小型结构的成本,但开销与结构大小无关。如果一个人使用像上面这样的结构,那么一个人可以有效地使用大小为几千字节的结构,以避免必须制作临时副本。

于 2013-03-05T17:56:38.840 回答