C# 不可变堆栈的使用
我已经看到今天宣布发布稳定版本的NuGet软件包C# 不可变堆栈的使用,c#,stack,immutability,C#,Stack,Immutability,我已经看到今天宣布发布稳定版本的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
类适用于您想要一个堆栈的任何情况,您可以保证该堆栈永远不会被修改,或者您需要一个