C# 不可变堆栈的使用

C# 不可变堆栈的使用,c#,stack,immutability,C#,Stack,Immutability,我已经看到今天宣布发布稳定版本的NuGet软件包 我想知道:不可变堆栈的用途是什么?我找不到这样有用的例子或场景。可能是我对不变性的理解。如果你能解释一下为什么这个东西可能有用,最好有一个具体的例子,那就太好了。这个集合库背后的想法是提供集合,当发生变化时,产生相同集合的新版本,所有旧版本的集合仍然有效 这类似于版本控制系统的工作方式:您可以更改代码,并将更改提交到服务器,从而生成源代码树的新版本。在此期间,您仍然可以签出任何以前的修订 对于不可变集合,可以通过保留指向不会更改的集合的指针来保持

我已经看到今天宣布发布稳定版本的NuGet软件包


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

这个集合库背后的想法是提供集合,当发生变化时,产生相同集合的新版本,所有旧版本的集合仍然有效

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

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

您可能会争辩说,这种情况对堆栈不太有用,因为您可能会使用堆栈来添加和检索每个项目一次,并保留顺序。通常,堆栈的使用与在单个执行线程上运行的代码密切相关


但是现在我正在输入,如果您有一个需要跟踪堆栈中某些状态的惰性解析器,我可以考虑一个特定的用例。然后,您可以将指向不可变堆栈的指针保存为初始解析期间的状态,而无需复制,并在解析延迟工作时检索该指针

想象这样一个场景:请求通过单个管道进入,并存储在堆栈中,因为最后一个请求将被视为优先级。然后我有一组线程化的请求使用者,每个使用者处理一种特定类型的请求。主封送处理代码构建堆栈,然后当所有使用者准备就绪时,将该堆栈传递给使用者并启动新堆栈

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


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

我喜欢不可变的集合,但它们通常感觉像是需要解决问题的解决方案。这可能是因为:他们努力高效地使用内存,但代价是使
foreach
循环和索引器在算法上更加复杂。考虑到内存的丰富性,很难证明这一成本是合理的。在网络上,关于不可变集合(除此之外)的成功使用的信息非常少。为了纠正这一点,我想我应该为这个3年多的问题添加一个答案,这个问题是关于一个我发现非常有用的案例

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

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

    public TreeNode(TreeNode parent) 
    {
        Parent = parent;
    }
}
公共类树节点
{
公共树节点父{get;private set;}
公共列表子节点{get;set;}
公共树节点(树节点父节点)
{
父母=父母;
}
}
我已经创建了很多次遵循这个基本模式的类,在实践中,它总是对我有用。最近,我开始做这样的事情:

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节点堆栈{get;private set;}
公共ImmutableStack父堆栈{get{return NodeStack.IsEmpty?ImmutableStack.Empty:NodeStack.Pop();}
公共树节点父{get{return ParentStack.IsEmpty?null:ParentStack.Peek();}
公共ImmutableArray子节点{get;set;}
公共树节点(树节点父节点)
{
如果(父项==null)
NodeStack=ImmutableStack.Empty;
其他的
NodeStack=parent.NodeStack.Push(这个);
}
}
出于各种原因,我更喜欢存储
TreeNode
实例的
ImmutableStack
,我可以遍历到根目录,而不是简单地引用
父实例

  • ImmutableStack
    的实现基本上是一个链表。
    ImmutableStack
    的每个实例实际上都是链接列表上的一个节点。这就是为什么
    ParentStack
    属性只是
    Pop
    NodeStack
    ParentStack
    将返回与
    Parent.NodeStack
    相同的
    ImmutableStack
    实例
  • 如果存储对
    父节点的引用
    树节点
    ,则很容易创建一个循环,该循环将导致查找根节点等操作永远不会终止,并且可能会导致堆栈溢出或无限循环。另一方面,
    ImmutableStack
    ,可以通过
    Pop
    -ing提升,保证它将终止。
    • 您使用集合(
      ImmutableStack
      )的事实意味着您可以通过简单地确保在构造
      TreeNode
      时,父级
      TreeNode
      不会出现两次来防止循环的发生
这只是开始。我认为
ImmutableStack
使树操作更简单、更安全。您可以(安全地)在其上使用LINQ。想知道一个
TreeNode
是否是另一个的后代吗?您只需执行
ParentStack.Any(node=>node==otherNode)

更一般地说,
ImmutableStack
类适用于您想要一个
堆栈的任何情况,您可以保证该堆栈永远不会被修改,或者您需要一个