一个不可改变的types能改变它的内部状态吗?

问题很简单。 一个可以改变其内部状态而不从外部观察的types可以被认为是不可变的吗?

简单的例子:

public struct Matrix { bool determinantEvaluated; double determinant; public double Determinant { get //asume thread-safe correctness in implementation of the getter { if (!determinantEvaluated) { determinant = getDeterminant(this); determinantEvaluated = true; } return determinant; } } } 

更新 :澄清线程安全问题,因为它是造成分心。

是的,不可变的可以改变它的状态,只要软件的其他组件(通常是caching) 看不到变化。 很像量子物理学:一个事件应该有一个观察者是一个事件。

在你的情况下,一个可能的实现是这样的:

  public class Matrix { ... private Lazy<Double> m_Determinant = new Lazy<Double>(() => { return ... //TODO: Put actual implementation here }); public Double Determinant { get { return m_Determinant.Value; } } } 

请注意, Lazy<Double> m_Determinant 具有更改状态

 m_Determinant.IsValueCreated 

然而,这是不可观测的

这取决于。

如果您将客户端代码或推理的作者logging为客户端代码的作者,那么您关心的是组件的接口(即其外部可观察的状态和行为),而不关注其实现细节(如内部表示)。

从这个意义上讲,即使它caching了状态,即使是懒惰地初始化等,只要这些变化不是外部可观察的,types也是不可变的。 换句话说,如果一个types在通过公共接口(或其他预期用例,如果有的话)使用时performance为不可变的,那么这个types是不可变的。

当然,这可能是棘手的(可变的内部状态,你可能需要关心线程的安全性, 序列化/编组行为等)。 但是,假设你确实做到了(至less在你需要的范围内),没有理由考虑这种不可变的types。

显然,从编译器或优化器的angular度来看,这样的types通常不被认为是不可变的(除非编译器足够智能或者具有某些“帮助”,如提示或某些types的先验知识)以及预期的任何优化对于不可变的types可能不适用,如果是这样的话。

我将在这里引用Clojure作者Rich Hickey的话 :

如果一棵树落在树林里,它会发出声音吗?

如果一个纯函数改变了一些本地数据以产生一个不可变的返回值,那好吗?

由于性能的原因,将暴露的API的对象进行变异是完全合理的。 关于不变对象的重要之处在于它们对外的不可变性。 所有封装在其中的东西都是公平的游戏。

在像C#这样的垃圾收集语言中,由于GC,所有对象都有一些状态。 作为一个消费者,通常不应该关心你。

我会坚持我的脖子

不,不可变对象不能在C#中改变其内部状态,因为观察其内存是一个选项,因此您可以观察到未初始化的状态。 certificate:

 public struct Matrix { private bool determinantEvaluated; private double determinant; public double Determinant { get { if (!determinantEvaluated) { determinant = 1.0; determinantEvaluated = true; } return determinant; } } } 

然后…

 public class Example { public static void Main() { var unobserved = new Matrix(); var observed = new Matrix(); Console.WriteLine(observed.Determinant); IntPtr unobservedPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof (Matrix))); IntPtr observedPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Matrix))); byte[] unobservedMemory = new byte[Marshal.SizeOf(typeof (Matrix))]; byte[] observedMemory = new byte[Marshal.SizeOf(typeof (Matrix))]; Marshal.StructureToPtr(unobserved, unobservedPtr, false); Marshal.StructureToPtr(observed, observedPtr, false); Marshal.Copy(unobservedPtr, unobservedMemory, 0, Marshal.SizeOf(typeof (Matrix))); Marshal.Copy(observedPtr, observedMemory, 0, Marshal.SizeOf(typeof (Matrix))); Marshal.FreeHGlobal(unobservedPtr); Marshal.FreeHGlobal(observedPtr); for (int i = 0; i < unobservedMemory.Length; i++) { if (unobservedMemory[i] != observedMemory[i]) { Console.WriteLine("Not the same"); return; } } Console.WriteLine("The same"); } } 

将types指定为不可变的目的是build立以下不variables:

  • 如果一个不可变types的两个实例被观察到是相等的,那么任何公共可观察的对一个实例的引用都可以被replace为对另一个的引用而不会影响其中任一个的行为。

因为.NET提供了比较任何两个引用的相等性的能力,所以不可能在不可变实例之间达到完美的等价关系。 尽pipe如此,如果将引用平等检查视为在类对象所负责的事物之外,上述不variables仍然非常有用。

请注意,在这个规则下,子类可以定义除包含在不可变基类中的字段,但不能以违反上述不变的方式暴露它们。 此外,一个类可以包含可变字段,只要它们不会以任何影响类的可见状态的方式进行改变。 考虑一下Java的string类中的hash字段。 如果它不为零,则string的hashCode值等于该字段中存储的值。 如果它为零,则string的hashCode值是对由string封装的不可变字符序列执行某些计算的结果。 将上述计算的结果存储到hash字段中不会影响string的散列码; 只会加速对价值的重复要求。