![]() |
1
53
简短回答:不,值类型不是定义不变的。 结构和类都可以是可变的或不可变的。 所有四种组合都是可能的。如果结构或类具有非只读公共字段、带setter的公共属性或设置私有字段的方法,则该结构或类是可变的,因为您可以更改其状态,而无需创建该类型的新实例。 长答案:首先,不变性问题只适用于具有字段或属性的结构或类。最基本的类型(数字、字符串和null)本质上是不可变的,因为它们没有任何(字段/属性)可更改。5是5是5。5上的任何操作只返回另一个不可变值。
有些人似乎混淆了不可变性和值类型是通过值(因此它们的名称)而不是通过引用传递的事实。
在这种情况下
为什么?
应该
值类型是不可变的吗?有很多原因。。。看见
this question
. 主要是因为可变值类型会导致各种不太明显的错误。在上面的例子中,程序员可能已经预料到了
更新:您的示例包括:
和
那个
认为
您正在修改某些内容,而有时您实际上正在修改副本,这会导致意外的错误。如果
更新:关于您重新措辞的请求。。。我想我知道你的意思。在某种程度上,您可以“认为”结构是 内部 不可变,即修改结构等同于用修改后的副本替换它。据我所知,它甚至可能是CLR在内存中内部执行的操作。(闪存就是这样工作的。你不能只编辑几个字节,你需要将一整块千字节读入内存,修改你想要的几个字节,然后写回整个块。)然而,即使它们是“内部不可变的”,这也是一个实现细节,对于作为结构用户的美国开发人员(如果你愿意的话,他们的接口或API),他们 可以 可以改变。我们不能忽视这一事实而“认为它们是不变的”。 在一条评论中,您说“您不能引用字段或变量的值”。假设每个结构变量都有不同的副本,这样修改一个副本不会影响其他副本。这并不完全正确。如果出现以下情况,则不可更换下面标记的线路:。。。
令人困惑坚持使用不可变的值类型,就可以消除修改其中任何一种类型的冲动。 更新:修复了第一个代码示例中的打字错误 |
![]() |
2
11
将类型定义为值类型表示运行时将复制值,而不是对运行时的引用。另一方面,可变性取决于实现,每个类都可以根据自己的需要实现它。 |
![]() |
3
8
您可以编写可变的结构,但最好的做法是使值类型不可变。 例如,在执行任何操作时,instance DateTime始终创建新实例。点是可变的,可以更改。 回答你的问题:不,它们在定义上不是不变的,这取决于它们是否应该是可变的。例如,如果它们应该用作字典键,那么它们应该是不可变的。 |
![]() |
4
5
如果你的逻辑足够深入,那么 全部的 类型是不可变的。当您修改引用类型时,您可能会认为您实际上是在将新对象写入同一地址,而不是修改任何内容。 或者你可以争辩说,在任何语言中,任何东西都是可变的,因为有时以前用于一件事的内存会被另一件事覆盖。 有了足够的抽象,忽略了足够的语言特征,你可以得出任何你喜欢的结论。 这没有抓住重点。根据.NET规范,值类型是可变的。你可以修改它。
但我还是一样。变量
在类似于具有不可变变量的函数式语言的情况下,这是不合法的。那我就不可能了。一旦变量被声明,它就有一个固定的值。
在.NET中,情况并非如此,没有什么可以阻止我修改
再仔细考虑一下,下面是另一个可能更好的例子:
在最后一行,根据您的逻辑,我们可以说我们实际上构造了一个新对象,并用该值覆盖旧对象。
但那是不可能的!要构造新对象,编译器必须设置
因此,要使所有结构都是不可变的,编译器必须欺骗并打破语言的规则。当然,如果我们愿意打破规则,我们可以证明一切。我可以证明所有的整数也是相等的,或者定义一个新的类会导致你的计算机着火。 只要我们遵守语言的规则,结构是可变的。 |
![]() |
5
4
那你不是在现实世界里工作吧?在实践中,值类型在函数之间移动时复制自身的倾向与不变性很好地结合在一起,但除非将它们设置为不可变,否则它们实际上不是不可变的,因为正如您所指出的,您可以像其他任何东西一样使用对它们的引用。 |
![]() |
6
4
不,他们不是:如果你看
然而,可以说所有的值类型 应该 可以使用不可变的API定义。 |
![]() |
7
2
我认为混淆之处在于,如果您有一个应该像值类型一样工作的引用类型,那么最好使其不可变。值类型和引用类型之间的关键区别之一是,通过ref类型上的一个名称所做的更改可以显示在另一个名称中。值类型不会发生这种情况:
生产:
现在,如果你有一个你想要值语义的类型,但是不想让它成为一个值类型——也许是因为它需要的存储太多或者什么,你应该认为不可变是设计的一部分。对于不可变的ref类型,对现有引用所做的任何更改都会生成一个新对象,而不是更改现有对象,因此您可以获得值类型的行为,即您持有的任何值都不能通过其他名称更改。 当然,System.String类就是此类行为的一个主要示例。 |
![]() |
8
2
去年,我写了一篇关于不制作结构可能遇到的问题的博文 不变的 The full post can be read here 这是一个例子,说明了事情会变得多么糟糕:
代码示例:
此代码的输出为:
现在让我们做同样的事情,但是用数组替换泛型
输出为:
从数组中访问元素时,运行时将直接获取数组元素,因此Update()方法对数组项本身有效。这意味着数组中的结构本身将被更新。
在第二个示例中,我们使用了泛型
换句话说,始终确保您的结构是不可变的,因为您永远无法确定何时将创建副本。大多数情况下,这是显而易见的,但在某些情况下,它真的会让你大吃一惊。。。 |
![]() |
9
1
不,他们不是。例子:
在这个例子中
现在
这是不同的。
与对象等效的是复制构造函数:
这里是
|
![]() |
10
1
不,值类型是 不 根据定义是不变的。 首先,我应该问一个问题“值类型的行为是否像不可变类型?”而不是问它们是否是不可变的——我想这会引起很多混乱。
[待续……] |
![]() |
11
1
要定义类型是可变的还是不可变的,必须定义“类型”所指的内容。当声明引用类型的存储位置时,该声明仅分配空间来保存对存储在别处的对象的引用;该声明不会创建所讨论的实际对象。尽管如此,在大多数讨论特定引用类型的上下文中,都不会讨论特定的引用类型 保存引用的存储位置 ,而是 由该引用标识的对象 . 可以写入包含对象引用的存储位置这一事实并不意味着对象本身是可变的。
相反,当声明值类型的存储位置时,系统将在该存储位置内为该值类型持有的每个公共或私有字段分配嵌套的存储位置。值类型的所有内容都保存在该存储位置。如果定义了一个变量
我认为将值类型“instance”视为字段比它们所持有的值更有帮助。根据该定义,存储在可变存储位置且存在任何非默认值的任何值类型都将
总是
无论它是如何声明的,都是可变的。一个声明
如果
如果有线程读取
|
![]() |
12
0
当对象/结构以无法更改数据的方式传递到函数中时,它们是不可变的,并且返回的结构是
|
|
Henry Vonfire · 如何在Slick中实现值类型? 10 年前 |