如何使用属性处理c#中的值类型?
我有一个名为如何使用属性处理c#中的值类型?,c#,properties,value-type,C#,Properties,Value Type,我有一个名为Tank的小类,它有一个名为Location的公共成员,它是一个矩形(一个结构)。当我写作时: Tank t = new Tank(); t.Location.X+=10; 一切正常,坦克移动 但是在我将成员更改为属性后,我不能再使用此语法了。它不会编译,因为t.Location现在是一个属性(即函数)并返回位置的临时副本(因为它是一个值类型) 我现在可以使用位置的唯一方法是执行以下操作: k = t.Location k.X +=10; t.Location = k; 有没有
Tank
的小类,它有一个名为Location
的公共成员,它是一个矩形(一个结构)。当我写作时:
Tank t = new Tank();
t.Location.X+=10;
一切正常,坦克移动
但是在我将成员更改为属性后,我不能再使用此语法了。它不会编译,因为t.Location
现在是一个属性(即函数)并返回位置的临时副本(因为它是一个值类型)
我现在可以使用位置的唯一方法是执行以下操作:
k = t.Location
k.X +=10;
t.Location = k;
有没有什么方法可以帮助我避免编写这种难看的代码,并使用直观的a+=10代码>语法 来自@Servy
“结构是不可变的”不,它们不是。在大多数情况下,它们应该是不变的,但它们并不是天生不变的。这里固有的问题是,属性返回结构的副本,而不是对结构的引用。如果C#中有ref返回的语法,那么这是可能的
从根本上讲,这不起作用的原因是结构是不可变的。一旦它们被制造出来,就这样了。因此,无法部分重新分配结构。这就像是试图换掉你的腿。你不能。这是你的一部分,而你也跟着来了
我认为您唯一能做的就是实现自己的X和Y属性,这样:
public double LocationX
{
get
{
return Location.X;
}
set
{
Location = new Rectangle(value,Location.Y);
}
}
显然,您也需要将此映射到Y,但这应该允许您想要的内容(但不要期望它是快速或高效的!)
虽然这回答了您当前的问题,但我想就您的模型提出几点看法。我会考虑不尝试更新这样的运动。从面向对象的角度来看,你的坦克是它自己的目标,应该管理它自己的位置。给它一个移动指令,然后让它更新自己的位置
e、 g:
这使您有更多的自由,并允许坦克根据您提供的逻辑验证对其提出的任何请求。当您开始使用属性在二维和三维空间中编程时,此问题非常常见。通常,最好的解决方法是在两个向量结构或两个不同的结构之间实现加法,这两个结构将以逻辑方式相加(在本例中,您将在二维向量和矩形之间实现加法以偏移其位置-您不会将两个矩形相加)
这样,您就可以编写:
myTank.Location += new Vector2(10, 0);
虽然仍然有点笨重,但它允许您在一次操作中对两个组件进行更改。理想情况下,添加的矢量将是一个速度矢量,您可以使用它来更新坦克的位置。我建议制定一种移动坦克的方法
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或其他任何东西。核心区别在于属性被分类为函数,而字段被分类为变量。函数成员调用开始
解决方法是使用字段或后备存储,而不是像您所做的那样使用属性。人们应该避免创建可变的值类型,因为行为通常很难预测和/或有时完全不一致
下面是一些基本细节,规范中的相关章节有助于描述您正在经历的行为
C#4.0第1.6.7.2节
集合访问器对应于具有一个名为
值和无返回类型
get访问器对应于带有返回的无参数方法
属性类型的值
现在切换到7.5.5功能成员调用,相关部分:
如果[函数成员]是在
值类型:
如果[实例表达式]未分类为变量,则
创建[instance expression]类型的临时局部变量,[instance expression]的vlue为
分配给该变量。然后将[实例表达式]重新分类为对
这个临时变量。临时变量如下所示可访问
在[功能成员]内,但不以任何其他方式因此,仅当[实例表达式]为true时
变量调用方是否可以观察到
[职能成员]对此做出了回应。
如果类或结构类型变量公开值类型字段,而该值类型将其内容公开为字段,则对这些字段的操作可以像对周围变量类型的操作数一样高效地执行。如果值类型作为属性公开,那么最好的方法通常是:
var temp = t.Location;
temp.X += 4;
t.Location = temp;
delegate void ActByRef<T1>(ref T1 p1);
void ActOnLocation(ActByRef<Point> proc)
{ proc(ref _Location); }
// Add 5 to X
myTank.ActOnLocation( (ref Point loc) => loc.X += 5 );
不太优雅,但相对清晰,效率也不太低。另一种方法是让油箱暴露一种方法AdjustLocation
,类似于:
var temp = t.Location;
temp.X += 4;
t.Location = temp;
delegate void ActByRef<T1>(ref T1 p1);
void ActOnLocation(ActByRef<Point> proc)
{ proc(ref _Location); }
// Add 5 to X
myTank.ActOnLocation( (ref Point loc) => loc.X += 5 );
或
注意,在后一种情况下,lambda中既不使用YSpeed
,也不使用this
,也不使用任何其他局部变量;相反,YSpeed
作为ref
参数传递。因此,即使上面的代码运行了一百万次,系统也只需要生成一个委托,然后每次都可以重复使用
如果结构较大,上述方法可能比使用临时变量的方法更快。虽然开销可能大于复制小结构的成本,但开销与结构大小无关。如果使用上述结构,可以有效地使用大小为数千字节的结构,以避免制作临时副本。我建议使用Tank.Offset(int x,int y)
作为替代方法,并保持矩形的私有性。你的偏移代码会修改坦克的位置。移动看起来像是你在坦克上执行的操作,因此是一种方法。Ins