为什么int
和double
是不可变的?每次要更改值时返回新对象的目的是什么?
我问的原因是因为我正在上课:BoundedInt
,它有一个值以及一个上限和下限。所以我想知道:我是否应该使这种类型不变? (或者应该是struct
?)
答案 0 :(得分:34)
首先:
每次要更改值时返回新对象的目的是什么?
我认为你可能会误解价值类型的运作方式。这可能不像你想象的那样昂贵的操作;它只是覆盖数据(而不是例如动态分配新内存)。
其次:这是一个非常简单的例子,说明为什么数字是不可变的:
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
。
当然,可以采用另一种方式,将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
基本上,值的不变性是高度直观的东西。相反的是非常不直观的。
我认为这是主观的,但公平地说;)
答案 1 :(得分:7)
作为一个可变对象,您必须在更改它之前锁定int
变量(在任何从不同线程写入您的int的多线程代码中)。
为什么呢?假设您正在增加int
,如下所示:
myInt++
引擎盖下,这是一个32位数字。从理论上讲,在32位计算机上你可以加1,这个操作可能是原子的;也就是说,它将在一个步骤中完成,因为它将在CPU寄存器中完成。不幸的是,它不是;还有比这更多的事情。
如果另一个线程在增加中间时突变了这个数字怎么办?你的号码会被破坏。
但是,如果在增加对象之前创建对象的线程安全副本,请对线程安全副本进行操作,并在增量完成时返回新对象,保证增量是线程安全的。它不会受到原始对象上发生在其他线程上的任何操作的影响,因为您不再使用原始对象。实际上,您已使对象不可变。
这是函数式编程背后的基本原理;通过使对象不可变,并从函数返回新对象,您可以免费获得线程安全。
答案 2 :(得分:5)
整数变量是可变的。但是,整数文字是常量,因此是不可变的。
int i = 0;
// Mutation coming!
i += 3;
// The following line will not compile.
3 += 7;
使用readonly
可以使整数字段不可变。同样,整数属性可以是get-only。
答案 3 :(得分:3)
将BoundedInt
作为可变类型是有意义的,因为它表示在任何时间点都具有特定值的变量,并且该值可以更改但仅在某个范围内。
然而整数本身不是变量,因此它们不应该是可变的。
答案 4 :(得分:2)
任何具有值语义的东西都应该在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这样的变异方法。
答案 5 :(得分:0)
我正在与神经网络合作开展一个学术项目。这些网络使用双精度计算繁重。我在亚马逊云上在32个核心服务器上运行了好几天。在分析应用程序时,最重要的性能问题是双重分配! 拥有一个具有可变类型的专用命名空间是公平的。 "不安全"可以强制执行关键字以进行其他预防措施。