C#和不变性和只读字段…一个谎言?

我发现人们声称,在类中使用所有只读字段并不一定会使该类的实例不可变,因为即使在初始化(构造)之后,也有“方法”更改只读字段值。

怎么样? 什么方法?

所以我的问题是什么时候我们真的可以在C#中有一个“真正的”不可变的对象,我可以安全地在线程中使用?

也做匿名types创build不可变的对象? 有人说LINQ在内部使用不可变对象。 究竟如何?

你在那里问了五个问题。 我会回答第一个问题:

在类中具有所有只读字段并不一定使该类的实例不可变,因为即使在构build之后也有“方法”更改只读字段值。 怎么样?

施工后可以更改只读字段吗?

是的, 如果你有足够的信任来打破只读的规则

这是如何运作的?

你的进程中的每一个用户内存都是可变的。 像只读字段这样的约定可能会使某些位看起来是不可变的,但是如果你努力工作,就可以改变它们。 例如,您可以获取不可变的对象实例,获取其地址,并直接更改原始位。 这样做可能需要大量的内存pipe理器的内部实现细节的聪明和知识,但不知何故内存pipe理器设法改变内存,所以你也可以尝试。 如果您足够信任,您也可以使用“私人反思”来破解安全系统的各个部分。

根据定义, 完全可信的代码被允许违反安全系统的规则 。 这就是“完全可信”的意思。 如果完全可信的代码select使用私人reflection或不安全的代码来破坏内存安全规则,那么完全可信的代码就可以做到这一点。

请不要。 这样做是危险和混乱的。 内存安全系统的devise是为了让你更容易理解你的代码的正确性。 故意违反规则是一个坏主意。

那么,“只读”是一个谎言? 那么,假设我告诉过你, 如果每个人都遵守规则 ,每个人都会得到一块蛋糕。 蛋糕是谎言吗? 这种说法并不是 “你会得到一块蛋糕”的说法。 这就是说, 如果每个人都服从规则 ,你会得到一块蛋糕。 如果有人作弊,把你的切片,没有你的蛋糕。

是只读类的只读字段吗? 是的,但只有每个人都遵守规则 。 所以,只读字段不是“一个谎言”。 合同是,如果每个人都遵守制度的规则,那么这个领域就被认为是只读的。 如果有人违反规则,那么也许不是。 这并不表示“如果每个人都遵守规则,这个领域只是一个谎言!

一个你没有问的问题,但也许应该是,一个结构体的“只读”是否也是一个“谎言”。 请参阅使用公共只读字段为不可变结构工作吗? 对于这个问题的一些想法。 结构上的只读字段比类中的只读字段更重要。

至于其他问题 – 如果你问每个问题一个问题,而不是每个问题五个问题,我想你会得到更好的结果。

编辑:我已经集中在系统内工作的代码,而不是像埃里克提到的那样使用reflection等。

这取决于字段的types。 如果字段本身是不可变的types(例如String ),那就好了。 如果是StringBuilder那么即使字段本身不改变它们的值,对象也可能会改变,因为“embedded”对象可能会改变。

匿名types完全相同。 例如:

 var foo = new { Bar = new StringBuilder() }; Console.WriteLine(foo); // { Bar = } foo.Bar.Append("Hello"); Console.WriteLine(foo); // { Bar = Hello } 

所以,基本上如果你有一个types,你想正确不变,你需要确保它只涉及不可变的数据。

还有一个结构问题,它可以具有只读字段,但是仍然会通过重新赋值来暴露自己变异的方法。 这种结构的行为有点奇怪,取决于你所说的确切情况。 不好 – 不要这样做。

Eric Lippert写了很多关于不变性的文章 – 全是黄金,就像你期望的那样…读起来:)(我没有注意到,当我写这篇文章的时候,Eric正在为这个问题写一个答案,显然读了他的也回答!)

我认为埃里克的答案已经远远超出了原来问题的范围,甚至没有回答,所以我会试一试:

什么只读? 那么,如果我们谈论值types,很简单:一旦值types初始化并赋值,它永远不会改变(至less就编译器而言)。

当我们讨论使用readonly和引用types时,混淆开始了。 在这一点上,我们需要区分引用types的两个组件:

  • 指向你的对象所在的内存地址的“引用”(variables,指针)
  • 包含引用指向的数据的内存

对象的引用本身就是一个值types。 当您使用readonly与引用types时,您将引用您的对象是不可变的,而不是在对象所在的内存上强制不变。

现在,考虑包含值types和对其他对象的引用的对象,这些对象包含值types和对其他对象的引用。 如果要以所有对象的所有字段都是只读的方式来组成对象,那么本质上可以实现您所期望的不变性。

只读和线程安全是Eric Lippert解释的两种不同的概念。

也许“人民”听到了第三只手,并且很less沟通build设者在“只读”领域获得的特殊权利。 它们不是只读的。

不仅如此,他们不是单一任务。 一个类的构造函数可以根据需要多次修改该字段。 所有这些都没有违反任何“规则”。

构造函数也可以调用其他任意方法,将自己作为parameter passing。 所以,如果你是另一种方法,你不能确定这个字段是不是一成不变的,因为也许你是由该对象的构造函数调用的,而构造函数将在你完成后立即修改这个字段。

你可以自己尝试一下:

 using System; class App { class Foo { readonly int x; public Foo() { x = 1; Frob(); x = 2; Frob(); } void Frob() { Console.WriteLine(x); } } static void Main() { new Foo(); } } 

这个程序打印1,2。'F'在Frob读取后修改。

现在,在任何单一的方法体内,值必须是常数 – 构造函数不能委托修改访问其他方法或委托,所以直到一个方法返回,我敢肯定该字段将需要保持稳定。

以上所有内容都是关于课程的。 结构完全是另一回事。