如何实现基于IEnumerable、IEnumerator的自定义Linked List及排查迭代异常
问题原因
- 核心错误是你把
IEnumerable(可枚举集合)和IEnumerator(枚举器)的实现耦合在了同一个Lista类中:GetEnumerator()方法直接返回this,意味着所有遍历共享同一个Current状态。你在Add()方法中修改了Current使其指向链表最后一个节点,触发遍历时MoveNext()判断Current.nextRow为null直接返回false,所以foreach会立刻终止。 - 枚举逻辑不符合C#规范:标准枚举器初始位置在第一个元素之前,第一次调用
MoveNext()才会指向首个元素,你的实现逻辑跳过了首个元素,且没有在返回枚举器前重置Current到表头。 - 你的猜想是正确的,
this确实会返回当前类的可枚举实现,但因为枚举状态和集合自身状态绑定,才导致了遍历异常。
修复方案
方案1:临时快速修复(仅适合单线程无嵌套遍历场景)
仅修改GetEnumerator()方法,每次返回枚举器前先重置状态:
public IEnumerator GetEnumerator() { Reset(); return this; }
该方案缺点是嵌套遍历或者多线程遍历同一链表时,状态会互相干扰,导致异常。
方案2:标准实现(推荐)
拆分独立枚举器类,每次遍历返回新的枚举器实例,状态完全隔离,同时修复Add方法的逻辑:
public class Lista : IEnumerable { private Row _header; private int counter; public Lista() { _header = null; counter = 0; } public IEnumerator GetEnumerator() { return new ListaEnumerator(_header); } // 独立枚举器内部类 private class ListaEnumerator : IEnumerator { private Row _current; private readonly Row _header; public ListaEnumerator(Row header) { _header = header; _current = null; } public bool MoveNext() { _current = _current == null ? _header : _current.nextRow; return _current != null; } public void Reset() { _current = null; } object IEnumerator.Current { get { if (_current == null) throw new InvalidOperationException("枚举未启动或已结束"); return _current; } } } public void Add(Reservation data) { Row newRow = new Row { reservation = data }; if (counter == 0) { _header = newRow; } else { Row last = _header; while (last.nextRow != null) last = last.nextRow; last.nextRow = newRow; } counter++; } public Reservation GetReservation(int index) { int x = 0; foreach (Row row in this) { if (x == index) return row.reservation; x++; } return null; } public Reservation Remove(Reservation data) { if (_header == null) return null; Reservation reservation = null; if (_header.reservation.Equals(data)) { reservation = _header.reservation; _header = _header.nextRow; } else { foreach (Row row in this) { if (row.nextRow != null && row.nextRow.reservation.Equals(data)) { reservation = row.nextRow.reservation; row.nextRow = row.nextRow.nextRow; break; } } } if (reservation != null) counter--; return reservation; } }
额外修复了Remove方法的空引用风险,空链表直接返回,遍历中判断nextRow非空再访问。
内容的提问来源于stack exchange,提问作者Bruno Leitao
相关产品推荐
相关产品推荐

