C#和不变性和只读字段......谎言?

WPF*_*-it 44 .net c# readonly immutability

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

怎么样?有什么方法?

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

匿名类型也创建不可变对象吗?有人说LINQ在内部使用了不可变的对象.究竟怎么样?

Eri*_*ert 104

你问过那里有五个问题.我会回答第一个问题:

在类中拥有所有只读字段并不一定使该类的实例不可变,因为即使在构造之后,仍有"方法"来更改只读字段值.怎么样?

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

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

这是如何运作的?

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

根据定义,完全受信任的代码可以破坏安全系统的规则.这就是"完全信任"的含义.如果您完全信任的代码选择使用私有反射或不安全代码等工具来破坏内存安全规则,则允许完全受信任的代码执行此操作.

请不要.这样做既危险又令人困惑.内存安全系统旨在使您更容易推断代码的正确性; 故意违反其规则是一个坏主意.

那么,"只读"是谎言吗?好吧,假设我告诉过你,如果每个人都遵守规则,每个人都会得到一块蛋糕.蛋糕是骗人的吗?这个说法不是 "你会得到一块蛋糕"的说法.这就是声称,如果每个人都遵守这些规则,你就会得到一块蛋糕.如果有人作弊并采取你的切片,没有蛋糕给你.

readonly是一个只读的字段吗?是的,但只有每个人都遵守规则.因此,只读字段不是"谎言".合同是,如果每个人都遵守系统的规则,那么该领域被认为是只读的.如果有人违反规则,那么也许不是.这并没有使声明"如果每个人都遵守规则,那么这个领域只是一个骗局"!

你没有问过,但也许应该有的问题是,结构字段上的"只读"是否也是"谎言".请参阅对不可变结构使用公共只读字段是否有效?关于这个问题的一些想法.结构上的只读字段比类上的只读字段要简单得多.

至于你的其他问题 - 如果每个问题提出一个问题,我认为你会得到更好的结果,而不是每个问题提出五个问题.


Jon*_*eet 14

编辑:我专注于在系统"内部"工作的代码,而不是像Eric提到的那样使用反射等.

这取决于领域的类型.如果字段本身是不可变类型(例如String)那么那很好.如果是,StringBuilder则即使字段本身不更改其值,对象也可能看起来发生变化,因为"嵌入"对象可能会发生变化.

匿名类型完全相同.例如:

var foo = new { Bar = new StringBuilder() };
Console.WriteLine(foo); // { Bar = }
foo.Bar.Append("Hello");
Console.WriteLine(foo); // { Bar = Hello }
Run Code Online (Sandbox Code Playgroud)

所以,基本上如果你有一个你想要正确不可变的类型,你需要确保它只引用不可变数据.

还有结构问题可以有只读字段但仍然暴露通过重新分配来改变自己的方法this.根据您调用它们的确切情况,这种结构表现得有些奇怪.不好 - 不要这样做.

埃里克·利珀特(Eric Lippert)写了很多关于不变性的文章 - 它都是黄金,正如你所期待的那样......去读:)(当我写这篇文章时,我没有注意到埃里克正在写这个问题的答案.很明显看过他的回答!)


w.b*_*ian 13

我认为Eric的回答远远超出了原始问题的范围,甚至没有回答它,所以我会对它进行一次尝试:

什么 readonly?好吧,如果我们谈论的是值类型,那很简单:一旦值类型被初始化并给出一个值,它就永远不会改变(至少就编译器而言).

当我们谈论将readonly与引用类型一起使用时,混乱开始出现.在这一点上,我们需要区分引用类型的两个组件:

  • "引用"(变量,指针),指向对象所在的内存中的地址
  • 包含引用指向的数据的内存

对对象的引用本身就是一种值类型. 当您使用readonly与引用类型时,您正在引用对象不可变,而不是强制对象所在的内存不变.

现在,考虑一个包含值类型和对其他对象的引用的对象,这些对象包含值类型和对其他对象的引用.如果你要以一种所有对象中的所有字段都是只读的方式来构建对象,那么从本质上说,你可以实现你想要的不变性.

  • 没错,但我觉得你很迂腐.不可变的价值需要以某种方式存在,而那个时间是在建设期间.一旦构造了对象,该值就是不可变的(尽管应该说,为了安抚学生,不安全的代码和反射仍然可以改变这些不可变的值). (3认同)

Dar*_*rov 8

Readon和Thread Safety是两种不同的概念,正如Eric Lippert解释的那样.