为什么C#号码类型是不可变的?

为什么intdouble都是不可变的? 每次要更改值时返回新对象的目的是什么?

我问的原因是因为我正在创建一个类: BoundedInt ,它有一个值以及一个上限和下限。 所以我想知道:我是否应该使这种类型不变? (或者它应该是一个struct吗?)

首先:

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

我想你可能会错误地认为价值类型是如何运作的。 这可能不像你想象的那样昂贵的操作; 它只是覆盖数据(而不是例如动态分配新内存)。

其次:这是一个非常简单的例子,说明为什么数字是不可变的:

 5.Increase(1); Console.WriteLine(5); // What should happen here? 

当然,这是一个人为的例子。 所以让我们考虑一些更复杂的想法。

可变参考类型

首先,有一个:如果Integer是一个可变的引用类型怎么办?

 class Integer { public int Value; } 

然后我们可以得到这样的代码:

 class Something { public Integer Integer { get; set; } } 

和:

 Integer x = new Integer { Value = 10 }; Something t1 = new Something(); t1.Integer = x; Something t2 = new Something(); t2.Integer = t1.Integer; t1.Integer.Value += 1; Console.WriteLine(t2.Integer.Value); // Would output 11 

这似乎t2.Integer = t1.Integer直觉:线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 

基本上, 价值观的不变性是非常直观的 。 相反的是非常不直观的。

我猜这是主观的,但公平地说;)

作为一个可变对象,你必须在更改它之前锁定一个int变量(在从不同线程写入你的int的任何multithreading代码中)。

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

 myInt++ 

在引擎盖下,这是一个32位数字。 从理论上讲,在32位计算机上你可以加1,这个操作可能是primefaces的; 也就是说,它将在一个步骤中完成,因为它将在CPU寄存器中完成。 不幸的是,它不是; 还有比这更多的事情。

如果另一个线程在处于递增的中间时突变了这个数字怎么办? 你的号码会被破坏。

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

这是函数式编程背后的基本原理; 通过使对象不可变,并从函数返回新对象,您可以免费获得线程安全。

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

 int i = 0; // Mutation coming! i += 3; // The following line will not compile. 3 += 7; 

使用readonly可以使整数字段不可变。 同样,整数属性可以是get-only。

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

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

任何具有值语义的东西都应该在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 

这就是为什么我不喜欢大多数Vector库在其Vector结构中使用像Normalize这样的变异方法。

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