从外部代码的角度来看,一个可变结构是不可变的呢?

时间:2010-09-27 19:16:27

标签: c# .net struct immutability mutable

更新:在发布此问题之后我发现这个想法的主要缺点就是这种类型很容易使用不当。也就是说,必须以非常具体的方式使用该类型以获得任何好处。我最初想到的是这样的东西(坚持原始问题中的SquareRootStruct示例只是为了保持一致性):

class SomeClass
{
    SquareRootStruct _key;

    public SomeClass(int value)
    {
        _key = new SquareRootStruct(value);
    }

    public double SquareRoot
    {
        // External code would have to access THIS property for caching
        // to provide any benefit.
        get { return _key.SquareRoot; }
    }

    public SquareRootStruct GetCopyOfKey()
    {
        // If _key has cached its calculation, then its copy will carry
        // the cached results with it.
        return _key;
    }
}

// elsewhere in the code...
var myObject = new SomeClass();

// If I do THIS, only a COPY of myObject's struct is caching a calculation,
// which buys me nothing.
double x = myObject.GetCopyOfKey().SquareRoot;

// So I would need to do this in order to get the benefit (which is,
// admittedly, confusing)...
double y = myObject.SquareRoot;

所以,考虑到错误是多么容易,我倾向于认为Reed在他的评论中是正确的,这会更有意义作为一个阶级。


假设我有struct我想要具备以下特征:

  1. 不可变的外部有利位置
  2. 快速初始化
  3. 某些属性的延迟计算(和缓存)
  4. 显然,第三个特征意味着可变性,我们都知道这是不好的(假设“不要制作可变的值类型!”已经充分钻进了我们的脑袋)。但在我看来,只要可变部分仅在类型本身内部可见,并且从外部代码的角度来看,值总是相同的,这是可以接受的。

    这是我正在谈论的一个例子:

    struct SquareRootStruct : IEquatable<SquareRootStruct>
    {
        readonly int m_value;  // This will never change.
    
        double m_sqrt;         // This will be calculated on the first property
                               // access, and thereafter never change (so it will
                               // appear immutable to external code).
    
        bool m_sqrtCalculated; // This flag will never be visible
                               // to external code.
    
        public SquareRootStruct(int value) : this()
        {
            m_value = value;
        }
    
        public int Value
        {
            get { return m_value; }
        }
    
        public double SquareRoot
        {
            if (!m_sqrtCalculated)
            {
                m_sqrt = Math.Sqrt((double)m_value);
                m_sqrtCalculated = true;
            }
    
            return m_sqrt;
        }
    
        public bool Equals(SquareRootStruct other)
        {
            return m_value == other.m_value;
        }
    
        public override bool Equals(object obj)
        {
            return obj is SquareRootStruct && Equals((SquareRootStruct)obj);
        }
    
        public override int GetHashCode()
        {
            return m_value;
        }
    }
    

    现在,显然这是一个简单的例子,因为Math.Sqrt几乎肯定不足以在这种情况下考虑这种方法。这只是用于说明目的的一个例子。

    但我的想法是,这实现了我的三个目标,其中最明显的替代方法不会。具体做法是:

    • 可以在类型的构造函数中执行计算;但这可能达不到上述第二个目标(快速初始化)。
    • 可以对每个属性访问执行计算;但这可能达不到上述第三个目标(未来访问的计算结果的缓存)。

    所以,是的,这个想法会有效地导致内部可变的值类型。但是,就任何外部代码而言(正如我所看到的),它将显示不可变,同时带来一些性能优势(同样,我意识到上面的例子不< / em>适当地使用这个想法;我所说的“性能优势”将取决于计算实际上是否足够昂贵以保证缓存。)

    我错过了什么,或者这实际上是一个有价值的想法?

3 个答案:

答案 0 :(得分:1)

拥有内部使用变异的不可变数据结构没有任何问题(只要线程安全得到广泛宣传)。只是结构一直被复制,所以你不能对变异做很多事情。这在C / C ++中不是问题,因为结构通常通过引用传递,而在C#中,很少通过引用传递结构。由于很难推断出通过值传递的结构,因此在C#中不鼓励使用可变结构。

答案 1 :(得分:1)

您所描述的内容可能会有所作为,但有一个重要的警告:计算缓慢的结果可能会或可能不会在计算时存储。例如,如果对枚举返回的结构执行计算,则结果将存储在“临时”结构中,并将被丢弃而不是传播回存储在数据结构中的结构。

答案 2 :(得分:0)

这类似于Future。有人提到在C#4.0并行扩展中更好地支持Futures。 (比如在正常工作的同时在另一个核心/线程上计算它们)。