Working with LinkedListNode in C#
c# linkedlistnode: Learn how to use LinkedListNode<T> in C# to traverse, insert, and remove nodes efficiently in a LinkedList, with practical examples and performance...
c# linkedlistnode requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
In C#, LinkedListNode<T> is the fundamental building block of the LinkedList<T> collection. Each node holds a value and references to its predecessor and successor, forming a doubly linked list. Understanding how to work with LinkedListNode<T> directly gives you precise control over insertion, removal, and traversal without the overhead of index-based operations.
What LinkedListNode Represents in a Doubly Linked List
LinkedListNode<T> is a class that encapsulates a single element in a LinkedList<T>. It exposes three key properties: Value (the stored data), Previous (the preceding node or null if it's the first), and Next (the following node or null if it's the last). The List property points back to the owning LinkedList<T>.
Unlike List<T> which stores elements in a contiguous array, a linked list stores nodes independently in memory. This means you can insert or remove a node in O(1) time if you already have a reference to the node, but you cannot access elements by index. The node references themselves are what make these operations possible.
LinkedList<string> list = new LinkedList<string>(); list.AddLast("first"); list.AddLast("second"); LinkedListNode<string> secondNode = list.Last; Console.WriteLine(secondNode.Value); // second Console.WriteLine(secondNode.Previous.Value); // first Console.WriteLine(secondNode.Next == null); // True
Accessing Node Values and Neighbors
The Value property is mutable, so you can update the data stored in a node without changing its position in the list. The Previous and Next properties are read-only and reflect the current state of the list. When a node is detached from a list, these properties become null.
A common mistake is to assume that Previous and Next remain valid after structural modifications. For example, if you remove a node from the middle of the list, its Previous and Next are set to null, and the adjacent nodes are linked together. If you still hold a reference to the removed node, you can no longer use it to traverse the original list.
LinkedList<int> numbers = new LinkedList<int>(); numbers.AddLast(1); numbers.AddLast(2); numbers.AddLast(3); LinkedListNode<int> node2 = numbers.Find(2); numbers.Remove(node2); Console.WriteLine(node2.Previous == null); // True Console.WriteLine(node2.Next == null); // True
Traversing a LinkedList Using LinkedListNode
Traversal is straightforward using the Next property from the first node. Because the list is doubly linked, you can also traverse backward from the last node using Previous. This is useful for reverse iteration without allocating a separate array.
LinkedList<string> tasks = new LinkedList<string>(); tasks.AddLast("compile"); tasks.AddLast("test"); tasks.AddLast("deploy"); for (LinkedListNode<string> node = tasks.First; node != null; node = node.Next) { Console.WriteLine(node.Value); } // Backward traversal for (LinkedListNode<string> node = tasks.Last; node != null; node = node.Previous) { Console.WriteLine(node.Value); }
Note that First and Last are properties on LinkedList<T> that return the corresponding node or null if the list is empty. The Find method returns the first node containing a value equal to the specified value, or null if not found. This is an O(n) operation because the list is not indexed.
Inserting and Removing Nodes with LinkedListNode References
One of the primary reasons to use LinkedListNode<T> is to perform constant-time insertions and removals when you have a reference to a specific node. The LinkedList<T> class provides methods like AddBefore, AddAfter, AddFirst, and AddLast, all of which accept a node reference or a value.
LinkedList<string> list = new LinkedList<string>(); LinkedListNode<string> root = list.AddFirst("root"); list.AddAfter(root, "child"); list.AddBefore(root, "parent"); // Result: parent, root, child
When you have a node, inserting before or after it is O(1) because the list only needs to adjust the Previous and Next references of the affected nodes. The same applies to removal: Remove(node) detaches the node in O(1). However, if you only have the value and need to find the node first, the total cost becomes O(n).
LinkedListNode<string> target = list.Find("child"); if (target != null) { list.AddBefore(target, "grandchild"); }
Be careful when modifying a list while iterating with a for loop that uses node.Next. If you remove the current node, node.Next becomes null after removal, so you must capture the next node before removing the current one.
LinkedListNode<string> node = list.First; while (node != null) { LinkedListNode<string> next = node.Next; if (node.Value.StartsWith("temp")) { list.Remove(node); } node = next; }
Performance Characteristics of LinkedListNode Operations
Understanding the performance of LinkedListNode<T> is essential for deciding when to use it. The key advantage is O(1) insertion and removal when you have the node reference. This contrasts with List<T>, where inserting or removing an element in the middle requires shifting subsequent elements, an O(n) operation.
However, finding a node by value is O(n) because the list is not indexed. Accessing an element by position is also O(n) since you must traverse from the head or tail. The memory overhead is higher than an array-based list because each node stores two references plus the object overhead.
| Operation | LinkedList<T> | List<T> |
|---|---|---|
| Access by index | O(n) | O(1) |
| Find by value | O(n) | O(n) |
| Insert at known node | O(1) | O(n) (shifting) |
| Remove at known node | O(1) | O(n) (shifting) |
| Memory per element | Two references + object | One reference |
These characteristics make LinkedList<T> suitable for scenarios where you frequently insert or remove nodes at arbitrary positions, especially when you already have node references. For most other cases, List<T> is more cache-friendly and provides faster iteration due to contiguous memory allocation.
When to Use LinkedListNode Instead of Other Collection Types
Choose LinkedList<T> and its nodes when your workload is dominated by insertions and deletions at known positions, and you do not need index-based access. A classic example is implementing a queue or a deque where you add to one end and remove from the other. LinkedList<T> provides O(1) operations at both ends, while Queue<T> and Stack<T> are optimized for single-ended access.
Another scenario is building a custom LRU cache. You need to move an item to the front whenever it is accessed. With LinkedListNode<T>, you can remove the node and add it to the front in O(1) time, provided you store the node reference in a dictionary keyed by the item. This avoids the O(n) removal cost of a List<T>.
If you need to access elements by index frequently, or if your collection size is small and you value simplicity, List<T> is usually the better choice. The performance difference for small collections is negligible, and the reduced memory footprint and better cache locality of List<T> often win in practice.
Common Pitfalls with LinkedListNode References
One common pitfall is holding onto a node reference after the list has been modified. If you insert or remove other nodes, the Previous and Next of your referenced node may change, but the node itself remains valid. However, if you remove the node itself, it becomes detached and cannot be used to navigate the list. Always re-fetch nodes from the list when you need to traverse after structural changes.
Another issue is using Find in a loop that also modifies the list. Since Find starts from the First node each time, repeated calls become O(n^2) if you are removing many nodes. Instead, iterate once and collect the nodes to remove, then remove them after the iteration.
List<LinkedListNode<string>> toRemove = new List<LinkedListNode<string>>(); for (LinkedListNode<string> node = list.First; node != null; node = node.Next) { if (node.Value == "obsolete") { toRemove.Add(node); } } foreach (LinkedListNode<string> node in toRemove) { list.Remove(node); }
Finally, remember that LinkedListNode<T> is a reference type. If you store nodes in a dictionary or another collection, the node's Value can be updated, but the node's position in the list is managed solely by the LinkedList<T>. Do not attempt to manually set Previous or Next; these are read-only and controlled by the list methods. Violating this invariant can corrupt the list structure and lead to undefined behavior.