【发布时间】:2012-05-13 13:06:52
【问题描述】:
我有一个结构体,其中有一个字段丢失了它的值。我可以将字段声明为静态并解决问题。我也可以将结构更改为类(不更改其他内容),这也解决了问题。我只是想知道这是为什么?
【问题讨论】:
-
你能出示你的代码吗?
-
我想我知道您要做什么,但请发布一些代码。
-
我怀疑为什么会出现这种情况,但您需要出示您的代码才能确认。
我有一个结构体,其中有一个字段丢失了它的值。我可以将字段声明为静态并解决问题。我也可以将结构更改为类(不更改其他内容),这也解决了问题。我只是想知道这是为什么?
【问题讨论】:
结构是按值传递的。换句话说,当你传递一个结构时,你传递的是它的值的副本。因此,如果您复制该值并对其进行更改,那么原始值将显示为未更改。您更改的是副本,而不是原件。
没有看到您的代码,我无法确定,但我认为这就是正在发生的事情。
类不会发生这种情况,因为它们是通过引用传递的。
值得一提的是,这就是为什么结构应该是不可变的——也就是说,一旦它们被创建,它们就不会改变它们的值。提供修改版本的操作返回新结构。
编辑:在下面的 cmets 中,@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);
【讨论】:
this 是有问题的。然而,带有暴露公共字段的普通旧数据结构通常比只允许通过构造函数更新的结构更好。 MyRect.Left += MyRect.Width; 比MyRect = new Rectangle(MyRect.Left+MyRect.Width, MyRect.Top, MyRect.Width, MyRect.Height); 更清晰,并且执行速度也更快。还值得注意的是,可变结构可以以线程安全的方式使用,而只能通过批量替换来改变的结构不能。
myShape.Bounds.X = 100?我的报告“无法修改 'G.Shape.Bounds' 的返回值,因为它不是变量”。至于辅助方法,他们在结构定义中添加了大量丑陋的代码来做一些在大多数情况下可以通过tempRect = myShape.Bounds; tempRect.X = 100; myShape.Bounds=tempRect; 更快速、更轻松地处理的事情。
this。因此,我认为结构应该将其字段包装在只读属性中的概念曾经是合理的建议......
当一个结构体传值时,系统会为被调用者复制一个结构体,所以它可以看到它的内容,也许可以修改自己的副本,但不能影响调用者副本中的字段。也可以通过ref 传递结构,在这种情况下,被调用者将能够使用调用者的结构副本,如果需要,可以对其进行修改,甚至可以通过ref 将其传递给其他可以执行类似操作的函数。请注意,被调用函数可以使调用者的结构副本可用于其他函数的唯一方法是通过ref 传递它,并且被调用函数在它传递结构的所有函数之前不能返回ref 也返回了。因此,调用者可以确信,由于函数调用而导致结构发生的任何变化都会在函数返回时发生。
这种行为与类对象不同;如果一个函数将一个可变类对象传递给另一个函数,则它无法知道该其他函数是否或何时会导致该对象立即或在将来的任何时间发生突变,即使在该函数完成运行之后也是如此。唯一能确定任何可变对象不会被外部代码改变的方法是,从该对象创建到被丢弃,成为该对象的唯一持有者。
虽然不习惯赋值语义的人最初可能会对按值传递结构只是为被调用函数提供一份它的副本,而将一个结构存储位置分配给另一个只是简单地复制结构的内容这一事实感到“惊讶”。 struct,值类型提供的保证非常有用。由于Point 是一个结构,因此可以知道像MyPoints[5].X += 1; 这样的语句(假设MyPoints 是一个数组)会影响MyPoints[5].X,但不会影响任何其他Point。可以进一步确定MyPoints[5].X 将改变的唯一方法是MyPoints 被另一个数组替换,或者写入MyPoints[5]。相比之下,Point 是一个类,而MyPoint[5] 曾经暴露于外部世界,唯一知道上述声明是否会影响X 的字段/属性X 的任何其他类型的存储位置Point 的方法是检查存在于代码中任何位置的Point 或Object 类型的每个存储位置,看它是否指向与MyPoints[5] 相同的实例。由于代码无法检查特定类型的所有存储位置,因此如果 Point[5] 曾经暴露于外部世界,这种保证是不可能的。
不过,结构有一个令人讨厌的问题:通常,如果允许被调用的代码写入相关结构,系统将只允许ref 传递结构。然而,结构方法调用和属性获取器接收this 作为ref 参数,但没有上述限制。相反,当在只读结构上调用结构方法或属性 getter 时,系统将制作结构的副本,通过 ref 将该副本传递给方法或属性 getter,然后丢弃它。由于系统无法知道方法或属性 getter 是否会尝试改变 this,因此在这种情况下它不会抱怨——它只会生成愚蠢的代码。但是,如果避免在除属性设置器之外的任何东西中更改 this(系统不允许在只读结构上使用属性设置器),则可以避免问题。
【讨论】: