Я люблю неизменные коллекции, но они часто чувствуют себя как решение, нуждающееся в решении проблемы. Они могут быть unexpectedly slow из-за how they are implemented: они очень стараются эффективно использовать память за счет того, что петли и индексы намного сложнее алгоритмически сложны. Насколько изощренной памятью, это может быть трудно оправдать эту стоимость. В Интернете есть информация о успешном использовании неизменных коллекций (кроме 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
-ing, гарантируя, что он будет завершен.
- Тот факт, что вы используете коллекцию (
ImmutableStack<T>
) означает, что вы можете предотвратить круговые петли происходило в первую очередь, просто убедившись, что parent
TreeNode
не происходит дважды при построении TreeNode
.
Это просто, чтобы начать с. Я думаю, что ImmutableStack<T>
упрощает и безопаснее дерево. Вы можете (безопасно) использовать LINQ на нем. Хотите знать, является ли один TreeNode
потомком другого? Вы можете просто сделать ParentStack.Any(node => node == otherNode)
В целом, ImmutableStack<T>
класса для любого случая, когда вы хотели бы Stack<T>
, что вы можете гарантировать, никогда не будут изменены, или вам нужен простой способ, чтобы получить «снимок» в Stack<T>
но не хотите создавать создавать кучу копий этого стека. Если у вас есть ряд вложенных шагов, вы можете использовать ImmutableStack<T>
для отслеживания прогресса, и поэтому, когда шаг будет завершен, он может передать работу на родительский шаг. Его неизменная природа особенно полезна, когда вы пытаетесь параллельно работать.
http://blogs.msdn.com/b/ericlippert/archive/2007/12/04/immutability-in-c-part-two-a-simple-immutable-stack.aspx –
Да, у меня есть видел эту статью. Но это не является причиной того, почему неизменяемый стек может быть полезен в некоторых контекстах. Еще раз, это может быть мое понимание неизменности, которое является неправильным. –