C#中使用Range语法优化foreach时迭代器方法报错的解决办法
解决Range枚举器在迭代器方法中无法使用的问题
问题场景
我尝试将代码中的foreach (var i in Enumerable.Range(0, _gridCount))替换为更简洁的foreach (var i in 0.._gridCount),但替换后抛出错误:
'foreach' statement cannot operate on enumerators of type 'RangeEnumerator' in 'async' or iterator methods because 'RangeEnumerator' is a 'ref' struct
相关代码:
迭代器方法
public IEnumerable<double> GridLevels() { var step = (_upperLimit - _lowerLimit) / _gridCount; foreach (var i in Enumerable.Range(0, _gridCount)) { var price = _lowerLimit + step * i; yield return price; } }
自定义Range扩展
public static class RangeExtensions { public static RangeEnumerator GetEnumerator(this Range range) => new(range); public ref struct RangeEnumerator { private readonly int _start; private readonly int _length; private int _count; public RangeEnumerator(Range range) { var (offset, length) = range.GetOffsetAndLength(int.MaxValue); (Current, _start, _length, _count) = (offset - 1, offset - 1, length, 0); } public bool MoveNext() { (var result, _count, Current) = (_count < _length, _count + 1, Current + 1); return result; } public void Reset() => (Current, _count) = (_start, 0); public int Current { get; private set; } public void Dispose() { } } }
错误原因
自定义的RangeEnumerator是ref struct类型,而带yield return的迭代器方法会被编译器编译为状态机类,状态机需要存储枚举器实例。但ref struct的特性是只能存在于栈上,不能被装箱、也不能作为类的字段存储,因此编译器禁止在迭代器方法中使用这类枚举器。
解决方案
方案1:改用for循环(推荐)
直接用for循环替代foreach,既保持简洁性,又完全规避ref struct的限制,同时性能最优:
public IEnumerable<double> GridLevels() { var step = (_upperLimit - _lowerLimit) / _gridCount; for (var i = 0; i < _gridCount; i++) { var price = _lowerLimit + step * i; yield return price; } }
方案2:修改自定义枚举器为普通值类型
去掉RangeEnumerator的ref修饰符,将其改为普通struct。这样枚举器可以被状态机存储,但会失去ref struct的栈分配优势:
public static class RangeExtensions { public static RangeEnumerator GetEnumerator(this Range range) => new(range); // 去掉ref关键字,改为普通struct public struct RangeEnumerator { private readonly int _start; private readonly int _length; private int _count; public RangeEnumerator(Range range) { var (offset, length) = range.GetOffsetAndLength(int.MaxValue); (Current, _start, _length, _count) = (offset - 1, offset - 1, length, 0); } public bool MoveNext() { (var result, _count, Current) = (_count < _length, _count + 1, Current + 1); return result; } public void Reset() => (Current, _count) = (_start, 0); public int Current { get; private set; } public void Dispose() { } } }
方案3:放弃迭代器延迟加载(适合小数据量)
将Range遍历逻辑提前生成完整集合,再返回集合而非使用yield return,这样可以保留Range的foreach写法,但会一次性加载所有元素:
public IEnumerable<double> GridLevels() { var step = (_upperLimit - _lowerLimit) / _gridCount; var levels = new List<double>(_gridCount); foreach (var i in 0.._gridCount) { levels.Add(_lowerLimit + step * i); } return levels; }
内容的提问来源于stack exchange,提问作者nop
相关产品推荐
相关产品推荐

