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

时间:2011-07-17 07:56:09

标签: c# .net immutability readonly

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

如何?有什么方法?

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

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

5 个答案:

答案 0 :(得分:105)

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

  

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

构建后是否可以更改只读字段?

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

这是如何运作的?

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

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

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

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

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

你没有问过,但也许应该有的问题是,结构字段上的“只读”是否也是“谎言”。有关该问题的一些想法,请参阅Does using public readonly fields for immutable structs work?。结构上的只读字段比类中的只读字段更容易。

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

答案 1 :(得分:14)

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

这取决于字段的类型。如果字段本身是不可变类型(例如String)那么那很好。如果它是StringBuilder,则即使字段本身不更改其值,对象也可以显示进行更改,因为“嵌入”对象可以更改。

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

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

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

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

Eric Lippert撰写了一篇great deal about immutability - 它是全金,正如你所期待的......去读:)(当我写这篇文章时,我没有注意到Eric正在写这个问题的答案。显然也读过他的答案!)

答案 2 :(得分:13)

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

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

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

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

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

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

答案 3 :(得分:8)

Readonly和Thread Safety是两个不同的概念,Eric Lippert解释了它。

答案 4 :(得分:0)

也许“人民”已经听过第三手资料并且对于构造函数对“只读”字段的特殊权限进行了很少的沟通。它们不是只读的。

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

构造函数也可以调用其他任意方法,将它们作为参数传递。所以如果你是那个其他方法,你就不能确定那个字段是否是不可变的,因为也许你被该对象的构造函数调用了,构造函数会在你完成后立即修改字段。 / p>

您可以自己尝试一下:

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。在Frob读取后修改'x'。

现在,在任何单个方法体中,值必须是常量 - 构造函数不能将修改访问委托给其他方法或委托,所以在方法返回之前,我很确定该字段需要保持稳定

以上所有内容都与课程有关。结构完全是另一个故事。