为什么C#数字类型是不可变的?

为什么C#数字类型是不可变的?,c#,types,immutability,C#,Types,Immutability,为什么ints和doubles是不可变的?每次要更改值时返回新对象的目的是什么 我问这个问题的原因是因为我正在创建一个类:BoundedInt,它有一个值和一个上限和下限。所以我在想:我应该让这个类型也不可变吗?(或者它应该是结构?)将BoundedInt作为可变类型是有意义的,因为它表示一个变量,该变量在任何时间点都有一个特定值,并且该值可以更改,但只能在一定范围内 但是整数本身不是变量,所以它们不应该是可变的。首先: 每次要更改值时返回新对象的目的是什么 我想你可能误解了价值类型是如何工作的

为什么
int
s和
double
s是不可变的?每次要更改值时返回新对象的目的是什么


我问这个问题的原因是因为我正在创建一个类:
BoundedInt
,它有一个值和一个上限和下限。所以我在想:我应该让这个类型也不可变吗?(或者它应该是
结构
?)

BoundedInt
作为可变类型是有意义的,因为它表示一个变量,该变量在任何时间点都有一个特定值,并且该值可以更改,但只能在一定范围内

但是整数本身不是变量,所以它们不应该是可变的。

首先:

每次要更改值时返回新对象的目的是什么

我想你可能误解了价值类型是如何工作的。这并不像你想象的那样是一个昂贵的操作;这仅仅是覆盖数据(与动态分配新内存相反)

第二:这里有一个非常简单的例子说明为什么数字是不可变的:

5.Increase(1);
Console.WriteLine(5); // What should happen here?
诚然,这是一个人为的例子。所以让我们考虑更多的相关概念。 可变引用类型 首先,这里有一个:如果
Integer
是一个可变的引用类型怎么办

class Integer
{
    public int Value;
}
然后我们可以有这样的代码:

class Something
{
    public Integer Integer { get; set; }
}
myInt++
以及:

这似乎违背了直觉:行
t2.Integer=t1.Integer
将简单地复制一个值(实际上是这样的;但“值”实际上是一个引用),因此
t2.Integer
将保持与
t1.Integer
无关

可变值类型 当然,这可以通过另一种方式来实现,即保持
Integer
作为值类型,但保持其可变性:

struct Integer
{
    public int Value;

    // just for kicks
    public static implicit operator Integer(int value)
    {
        return new Integer { Value = value };
    }
}
但现在让我们这样做:

Integer x = 10;

Something t = new Something();
t.Integer = x;

t.Integer.Value += 1; // This actually won't compile; but if it did,
                      // it would be modifying a copy of t.Integer, leaving
                      // the actual value at t.Integer unchanged.

Console.WriteLine(t.Integer.Value); // would still output 10
基本上,值的不变性是非常直观的。相反,这是非常不直观的


不过,公平地说,我想这是主观的;)

任何具有值语义的东西在C#中都应该是不可变的

可变类不能具有值语义,因为不能重写赋值运算符

MyClass o1=new MyClass();
MyClass o2=o1;
o1.Mutate();
//o2 got mutated too
//=> no value but reference semantics
可变结构很难看,因为您可以很容易地对临时变量调用一个变异方法。特别是属性返回临时变量

MyStruct S1;
MyStruct S2{get;set;}

S1.Mutate(); //Changes S1
S2.Mutate();//Doesn't change S2

这就是为什么我不喜欢大多数向量库在其向量结构中使用诸如Normalize之类的变异方法。

作为一个可变对象,您必须在更改变量之前锁定一个
int
变量(在从单独线程写入int的任何多线程代码中)

为什么??假设您正在递增一个
int
,如下所示:

class Something
{
    public Integer Integer { get; set; }
}
myInt++
在引擎盖下,这是一个32位数字。理论上,在一台32位计算机上,你可以向它添加1,这个操作可能是原子的;也就是说,它将在一个步骤中完成,因为它将在CPU寄存器中完成。不幸的是,事实并非如此;还有更多的事情要做

如果另一个线程在递增过程中突变这个数字,又会怎样呢?你的号码会被破坏

但是,如果在增量之前创建对象的线程安全副本,对线程安全副本进行操作,并在增量完成时返回新对象,则可以保证增量是线程安全的;它不会受到在其他线程上对原始对象执行的任何操作的影响,因为您不再使用原始对象。实际上,您已使对象不可变

int i = 0;

// Mutation coming!
i += 3;

// The following line will not compile.
3 += 7;
这是函数式编程背后的基本原则;通过使对象不可变,并从函数返回新对象,您可以免费获得线程安全。

整数变量是可变的。但是,整数文本是常量,因此是不可变的

int i = 0;

// Mutation coming!
i += 3;

// The following line will not compile.
3 += 7;

可以使用
readonly
使整型字段不可变。同样,只能得到整数属性。

我正在从事一个神经网络的学术项目。这些网络使用双精度进行大量计算。我在32台核心服务器上在亚马逊云上运行了几天。在分析应用程序时,最大的性能问题是分配double!!
拥有一个具有可变类型的专用名称空间是公平的。“不安全”关键字可以强制执行,以便采取额外的预防措施。

你可以用这些问题打开巨大的蠕虫罐头。我的建议是,在尝试将数据验证推入数据本身时要三思。验证在比底层数据类型更高的抽象级别上更有意义。例如,您可能需要将一个数据区域与另一个数据区域的值绑定。@Merlyn:对于具有隐式边界的数据,例如长度[0,∞), 角度[0,360],或掷骰子的结果[1,6]?我当然不想要-180cm高。@Seth:这些是不定义我们的架构的特殊情况。361或-1的值是否无效完全取决于它们被解释为什么,而不是它们本身的值。@Seth:绑定到类型的默认约束可能有用(例如,6面模具),但您通常需要自定义该约束。单位是一个相关的问题。更好的方法是使用约束解决/映射系统,或基于契约的编程框架(例如spec#)。你需要听听Bob叔叔在DotNetRocks上关于“OOP的未来”的讨论。我不确定接下来会发生什么。事实上,你展示的代码不是线程安全的。你需要使用
Interlocked.Increment
来确保这一点。我还应该提到,不可能锁定int,因为它不是引用类型。感谢你更改了答案,但很抱歉,它仍然不正确。
int
变量是可变的,这一点可以从您可以对其进行增量的事实中看出。
int
变量是可变的。并且,正如前面提到的,增量操作不是原子的。尽管它通常会作为
INC
opc实现