不可变类型可以改变其内部状态吗?

InB*_*een 44 c# immutability

问题很简单.可以改变其内部状态而不能从外部观察的类型是否可以被认为是不可变的

简化示例:

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;    
         }
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:澄清线程安全问题,因为它导致分心.

The*_*kis 33

这取决于.

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

从这个意义上说,即使一个类型缓存状态,即使它是懒惰地初始化等,类型也是不可变的 - 只要这些突变在外部是不可观察的.换句话说,如果类型在通过其公共接口(或其他预期用例,如果有)使用时表现为不可变,则该类型是不可变的.

当然,要做到这一点可能很棘手(有了可变的内部状态,你可能需要关注线程安全,序列化/编组行为等).但假设你确实做到了(至少你需要的程度),没有理由考虑这种类型是不可变的.

显然,从编译器或优化器的角度来看,这种类型通常不被认为是不可变的(除非编译器足够智能或具有一些"帮助",如某些类型的提示或先验知识)以及任何预期的优化对于不可变类型可能不适用,如果是这种情况.

  • 假设我第一次初始化行列式,需要200ms.然后我第二次访问它,需要2ms.相同的值返回两次,具有不同的性能.如果一个是O(n)而另一个是O(1),我们还能说两个调用都有相同的行为吗?该方法仍然是不可变的吗?不可变定义是否将性能作为标准? (2认同)
  • @CyrilGandon同样,如果给你一个不相关的方法,它被记录为纯粹的(只从你传递给它的状态读取并且没有引起外部副作用)并且它在第一次调用它时正常结束,但恰好导致你第二次打电话给一个完整的GC(导致终结器运行),你会说它的表现不同吗?你会说它不纯粹吗?我想你不会,因为我们*期望*GC是非确定性的(并且随时都可以启动),就像我们预期的那样*任何类型的性能在任何非实时系统中都是技术上不确定的. (2认同)

Dmi*_*nko 17

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

在您的情况下,可能的实现是这样的:

  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;
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

请注意,Lazy<Double> m_Determinant 具有变化的状态

m_Determinant.IsValueCreated 
Run Code Online (Sandbox Code Playgroud)

然而,这是不可观察的.


Ben*_*aum 8

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

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

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

为了性能原因,改变暴露API的对象是完全合理的,这些API对外部是不可变的.关于不可变对象的重要之处在于它们对外界的不变性.封装在其中的一切都是公平的游戏.

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


Mei*_*hes 5

我会伸出脖子......

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

public struct Matrix
{
    private bool determinantEvaluated;
    private double determinant;

    public double Determinant
    {
        get
        {
            if (!determinantEvaluated)
            {
                determinant = 1.0;
                determinantEvaluated = true;
            }

            return determinant;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后...

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 你有一点,这是一个可能的用例.因此,如果我们想从序列化和编组机制的角度使这种类型不可变,我想我们需要从序列化中明确地排除隐藏的可变字段,或者提供首先完全初始化实例的自定义序列化逻辑.(但这意味着我们仍然可以使其在编组时表现为不可变的.) (2认同)