使用不可变堆栈

时间:2013-09-26 09:49:39

标签: c# stack immutability

我已经看到post宣布今天发布了Microsoft.Bcl.Immutable NuGet软件包的稳定版本。

我想知道:不可变堆栈的用途是什么?我找不到一个有用的例子或场景。可能是我对不变性的理解。如果你能说明为什么这个东西可能有用,最好用一个具体的例子,这将是很好的。

3 个答案:

答案 0 :(得分:7)

这个集合库背后的想法是提供集合,当更改时生成相同集合的新版本,其中所有旧版本的集合仍然有效。

它类似于版本控制系统的工作方式:您可以更改代码,并将更改提交到服务器,从而生成新版本的源代码树。在此期间,您仍然可以查看以前的任何修订版本。

对于不可变集合,通过保持指向不会更改的集合的指针,您可以继续访问旧版本(本质上是不可变快照)。

你可能会争辩说这个场景对于堆栈不太有用,因为你可能使用堆栈来准确地添加和检索每个项目一次,并且还保留了订单。通常,堆栈的使用与在单个执行线程上运行的代码密切相关。

但是现在我正在打字,如果你有一个需要跟踪堆栈中某个状态的惰性解析器,我可以想到一个特定的用例。然后,您可以在初始分析期间保存指向不可变堆栈的指针,而不进行复制,并在解析延迟工作时检索它。

答案 1 :(得分:2)

想象一种情况,即请求进入单个管道并存储在堆栈中,因为最后一个请求将被视为优先级。然后我有一组线程请求消费者,每个消费者处理特定类型的请求。主编组代码构建堆栈,然后当所有消费者都准备就绪时,将该堆栈传递给消费者并开始新的堆栈。

如果堆栈是可变的,那么每个消费者将并行运行并且将共享相同的堆栈,由于其他消费者而在任何阶段都可以改变。需要锁定来控制堆栈中的弹出项目以确保它们保持同步。如果消费者花费很长时间来完成堆栈被锁定的操作,则所有其他操作都会被阻止。

通过拥有一个不可变的堆栈,每个消费者都可以自由地做它想要的堆栈,而无需关心其他消费者。从堆栈中弹出一个项目会产生一个新项目,而原始项目将保持不变。因此,不需要锁定,每个线程都可以自由运行而无需关心其他线程。

答案 2 :(得分:2)

我喜欢不可变的收藏品,但他们常常觉得这是一个需要解决问题的解决方案。由于unexpectedly slow,它们可能是how they are implemented:他们非常努力地使用内存,但代价是使foreach循环和索引器在算法上更加复杂。凭借丰富的记忆力,很难证明这一成本是合理的。在网上找不到关于成功使用不可变集合(ImmutableArray<T>除外)的信息。为了纠正这个问题,我想我会在这个3岁以上的问题中添加一个答案来解答我发现ImmutableStack<T>真正有用的案例。

考虑这个非常基本的树状结构:

public class TreeNode
{
    public TreeNode Parent { get; private set; }
    public List<TreeNode> ChildNodes { get; set; }

    public TreeNode(TreeNode parent) 
    {
        Parent = parent;
    }
}

我已经创建过多次,多次遵循这种基本模式的课程,而且在实践中,它总能为我工作。最近,我开始做这样的事情:

public class TreeNode
{
    public ImmutableStack<TreeNode> NodeStack { get; private set; }
    public ImmutableStack<TreeNode> ParentStack { get { return NodeStack.IsEmpty ? ImmutableStack<TreeNode>.Empty : NodeStack.Pop(); } }
    public TreeNode Parent { get { return ParentStack.IsEmpty ? null : ParentStack.Peek(); } }

    public ImmutableArray<TreeNode> ChildNodes { get; set; }

    public TreeNode(TreeNode parent)
    {
        if (parent == null)
            NodeStack = ImmutableStack<TreeNode>.Empty;
        else
            NodeStack = parent.NodeStack.Push(this);
    }
}

出于各种原因,我更倾向于存储ImmutableStack<T> TreeNode个我可以遍历到根目录的实例,而不仅仅是对Parent的引用实例

  • ImmutableStack<T>的实施基本上是一个链表。 ImmutableStack<T>的每个实例实际上都是链表上的节点。这就是ParentStack属性只是Pop NodeStack的原因。 ParentStack会返回与ImmutableStack<T>相同的Parent.NodeStack实例。
  • 如果存储对Parent TreeNode的引用,则很容易创建一个循环循环,这会导致查找根节点等操作永不终止,并且您可以获得堆栈溢出或无限循环。另一方面,ImmutableStack<T>可以通过Pop来提升,保证它将终止。
    • 您使用集合(ImmutableStack<T>)这一事实意味着您可以通过简单地确保parent TreeNode不会发生两次来防止循环发生构建TreeNode

那只是为了开始。我认为ImmutableStack<T>使树操作更简单,更安全。您可以(安全地)使用LINQ。想知道一个TreeNode是否是另一个的后代?你可以简单地做ParentStack.Any(node => node == otherNode)

更一般地说,ImmutableStack<T>类适用于任何您希望保证永远不会被修改的Stack<T>的情况,或者您需要一种简单的方法来获取&#34;快照& #34;一个Stack<T>,但不想创建创建该堆栈的一堆副本。如果您有一系列嵌套步骤,则可以使用ImmutableStack<T>来跟踪进度,因此当步骤完成后,它可以将工作移交给其父步骤。当你尝试并行工作时,它的不变性特别有用。