Uwielbiam niezmienne kolekcje, ale często czuję się jak rozwiązanie wymagające problemu. Mogą być unexpectedly slow ze względu na how they are implemented: bardzo starają się efektywnie wykorzystywać pamięć kosztem tworzenia pętli i indeksatorów o wiele bardziej złożonych algorytmicznie. Przy tak dużej ilości pamięci trudno jest uzasadnić ten koszt. W Internecie brakuje informacji o udanych zastosowaniach niezmiennych kolekcji (innych niż ImmutableArray<T>
). Próbując to naprawić, pomyślałem, że dodam odpowiedź na to ponad 3-letnie pytanie dotyczące przypadku, w którym stwierdziłem, że ImmutableStack<T>
jest naprawdę użyteczny.
Rozważmy to bardzo podstawowe drzewiastą strukturę:
public class TreeNode
{
public TreeNode Parent { get; private set; }
public List<TreeNode> ChildNodes { get; set; }
public TreeNode(TreeNode parent)
{
Parent = parent;
}
}
I utworzeniu klasy, które obserwować tą podstawowy wzór wiele, wiele razy, aw praktyce to zawsze pracował dla mnie. Niedawno zacząłem robić coś takiego:
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);
}
}
Z różnych powodów, Doszedłem do preferują Zapisywanie ImmutableStack<T>
z TreeNode
instancji, że mogę Ruch do korzeni, a nie tylko odniesienie do instancji Parent
. Wykonanie
ImmutableStack<T>
jest w zasadzie połączoną listą. Każde wystąpienie ImmutableStack<T>
jest tak naprawdę węzłem na połączonej liście. Dlatego właśnie właściwość ParentStack
jest tylko Pop
's NodeStack
. ParentStack
spowoduje wyświetlenie tego samego wystąpienia z ImmutableStack<T>
jako Parent.NodeStack
.
- Jeśli przechowujesz odniesienie do
Parent
TreeNode
, łatwo byłoby utworzyć okrągłą pętlę, która spowodowałaby takie operacje, jak znalezienie węzła głównego, który nigdy się nie zakończy, a otrzymasz przepełnienie stosu lub nieskończoną pętlę. Z drugiej strony, ImmutableStack<T>
może zostać wzniesiony przez Pop
-ing, gwarantując, że się zakończy.
- Fakt, że używasz Collection (
ImmutableStack<T>
) oznacza, że można zapobiec okrężne pętle z dzieje się w pierwszej kolejności, po prostu upewniając się parent
TreeNode
nie występuje dwukrotnie przy konstruowaniu TreeNode
.
Na początek. Myślę, że ImmutableStack<T>
sprawia, że operacje na drzewach są prostsze i bezpieczniejsze. Możesz (bezpiecznie) użyć LINQ na nim. Chcesz wiedzieć, czy jeden TreeNode
jest potomkiem innego? Można po prostu zrobić ParentStack.Any(node => node == otherNode)
Bardziej ogólnie, klasa ImmutableStack<T>
jest dla każdego przypadku, w którym chcieliby Państwo Stack<T>
że można zagwarantować, nigdy nie zostaną zmodyfikowane, czy trzeba w prosty sposób uzyskać „migawkę” na Stack<T>
ale nie chcę tworzyć, stwórz masę kopii tego stosu. Jeśli masz szereg zagnieżdżonych kroków, możesz użyć ImmutableStack<T>
, aby śledzić postępy, a kiedy krok zostanie ukończony, może przekazać pracę do kroku nadrzędnego. Jego niezmienny charakter jest szczególnie przydatny, gdy próbujesz pracować równolegle.
http://blogs.msdn.com/b/ericlippert/archive/2007/12/04/immutability-in-c-part-two-two-simple-immutable-stack.aspx –
Tak, mam widziałem ten artykuł. Nie podaje jednak powodu, dla którego niezmienny stos mógłby być użyteczny w niektórych kontekstach. Jeszcze raz to może być moje zrozumienie niezmienności, która jest zła. –