我有一个结构体,其中有一个失去其价值的字段。我可以将字段声明为静态并解决问题。我也可以将结构更改为类(不更改其他内容),这也解决了问题。我只是想知道这是为什么?
2 回答
结构是按值传递的。换句话说,当你传递一个结构时,你传递的是它的值的副本。因此,如果您复制该值并对其进行更改,那么原始值将显示为未更改。您更改了副本,而不是原件。
如果没有看到您的代码,我无法确定,但我认为这就是正在发生的事情。
类不会发生这种情况,因为它们是通过引用传递的。
值得一提的是,这就是为什么结构应该是不可变的——也就是说,一旦它们被创建,它们就不会改变它们的值。提供修改版本的操作返回新结构。
编辑:在下面的评论中,@supercat 建议可变属性可以更方便。然而,结构上的属性设置器也可能导致奇怪的失败。除非您深入了解结构的工作原理,否则这里有一个示例可能会让您大吃一惊。对我来说,完全避免可变结构就足够了。
考虑以下类型:
struct Rectangle {
public double Left { get; set; }
}
class Shape {
public Rectangle Bounds { get; private set; }
}
好的,现在想象一下这段代码:
myShape.Bounds.Left = 100;
也许令人惊讶的是,这根本没有效果!为什么?让我们以更长但等效的形式重新编写代码:
var bounds = myShape.Bounds;
bounds.Left = 100;
在这里更容易看到如何将 的值复制Bounds
到局部变量,然后更改其值。Shape
但是,在任何时候都不会更新原始值。
这是使所有公共结构不可变的非常有说服力的证据。如果您知道自己在做什么,可变结构可能会很方便,但我个人只是以这种形式真正将它们用作私有嵌套类。
正如@supercat 指出的那样,替代方案有点难看:
myShape.Bounds = new Rectangle(100, myShape.Bounds.Top,
myShape.Bounds.Width, myShape.Bounds.Height);
有时添加辅助方法更方便:
myShape.Bounds = myShape.Bounds.WithLeft(100);
当结构体通过值传递时,系统会为被调用者制作一个结构体的副本,这样它就可以看到它的内容,也许可以修改自己的副本,但不能影响调用者副本中的字段。也可以通过 传递结构ref
,在这种情况下,被调用者将能够使用调用者的结构副本,如果需要,可以对其进行修改,甚至可以将其传递ref
给可以执行类似操作的其他函数。请注意,被调用函数可以使调用者的结构副本对其他函数可用的唯一方法是通过 传递它ref
,并且被调用函数只有在将结构传递给的所有函数之后才能返回ref
也回来了。因此,调用者可以确信,由于函数调用而对结构发生的任何更改都将在它返回时发生。
这种行为与类对象不同;如果一个函数将一个可变类对象传递给另一个函数,它无法知道该函数是否或何时会导致该对象立即或在将来的任何时间发生突变,即使在函数完成运行之后也是如此。唯一能确定任何可变对象不会被外部代码改变的方法是,从创建到废弃的那一刻,成为该对象的唯一持有者。
虽然不习惯对语义赋值的人最初可能会对按值传递结构只是给被调用函数一个它的副本,并将一个结构存储位置分配给另一个只是复制结构的内容这一事实感到“惊讶”,但保证提供的值类型非常有用。由于Point
是一个结构,因此可以知道像MyPoints[5].X += 1;
(假设MyPoints
是一个数组)这样的语句会影响MyPoints[5].X
但不会影响任何其他Point
. 可以进一步保证,唯一MyPoints[5].X
会改变的方法是,要么MyPoints
被另一个数组替换,要么写入MyPoints[5]
. 相比之下,Point
是一个类和MyPoint[5]
曾经暴露于外部世界,了解上述语句是否会影响X
任何其他类型存储位置的字段/属性的唯一方法Point
是检查每个类型的存储位置Point
或Object
存在于代码中的任何位置,以查看是否它指向与 相同的实例MyPoints[5]
。由于代码无法检查特定类型的所有存储位置,因此如果Point[5]
曾经暴露于外部世界,这种保证将是不可能的。
ref
但是,结构有一个令人讨厌的问题:通常,如果允许调用的代码写入相关结构,系统只会允许结构通过。然而,结构方法调用和属性获取器this
作为ref
参数接收,但没有上述限制。相反,当在只读结构上调用结构方法或属性 getter 时,系统将复制该结构,将该副本传递ref
给方法或属性 getter,然后丢弃它。由于系统无法知道方法或属性 getter 是否会尝试 mutate this
,因此在这种情况下它不会抱怨——它只会生成愚蠢的代码。如果一个人避免变异this
但是,除了属性设置器(系统不允许在只读结构上使用属性设置器)之外的任何东西,都可以避免问题。