我正在用
Span<T>
类型尝试一些东西。我的目标是创建一个扩展方法来枚举 ReadOnlySpan<char>
的分裂,按如下方式使用:
var x = "foo,bar,baz".AsSpan();
var options = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries;
foreach (var word in x.EnumerateSplit(',', options))
{
// Should enumerate foo, bar and baz
}
但是,当我实现自己的枚举器类型(在场景后面封装了
Span<Range>.Enumerator
实例)时,在访问封装的枚举器 IndexOutOfRangeException
属性时会抛出 Current
。
我尝试创建最基本的枚举器,它只有封装逻辑,没有拆分机制:
public ref struct BasicEnumerator
{
// Encapsulated enumerator
private readonly Span<Range>.Enumerator _enumerator;
public Range Current => _enumerator.Current;
public BasicEnumerator(Span<Range>.Enumerator enumerator)
{
_enumerator = enumerator;
}
public bool MoveNext() => _enumerator.MoveNext();
// Called by foreach
public BasicEnumerator GetEnumerator() => this;
}
有了这个
BasicEnumerator
类型,我至少期望这段代码能够工作:
Span<Range> test = stackalloc Range[5];
var enumerator = new BasicEnumerator(test.GetEnumerator());
foreach (Range r in enumerator)
{
Console.WriteLine(r);
}
但是在第一次迭代时会抛出
IndexOutOfRangeException
。
我也尝试过更简单的方法,不使用
foreach
:
Span<Range> test = stackalloc Range[5];
var enumerator = new BasicEnumerator(test.GetEnumerator());
if (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
尝试访问
IndexOutOfRangeException
时仍抛出 enumerator.Current
。
请注意,当我使用
Span<Range>.Enumerator
代替时(以相同的方式),它可以工作:
Span<Range> test = stackalloc Range[5];
Span<Range>.Enumerator enumerator = test.GetEnumerator();
if (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
在调试时,我发现它不起作用,因为
Span<Range>.Enumerator.MoveNext
方法不会增加底层索引,但仍然返回 true
:
Visual Studio 调试截图
我真的不知道为什么要这样做。我想说这是一个参考问题,但我不知道为什么以及如何解决它。我试图找到有关类似用例的文档,但没有成功。
我正在使用.NET 8。
这是因为结构是值类型+你的
_enumerator
是readonly
结构。因此,当您调用 BasicEnumerator.MoveNext()
时,您将获得内部 _enumerator
结构体的 copy,然后调用该副本的
MoveNext()
,然后丢弃该副本。原始结构保持不变,因此它的 Current
仍然是 -1。
要解决此问题,请删除只读限定符
//remove `readonly`
private /*readonly*/ Span<Range>.Enumerator _enumerator;
错误就在这里
private readonly Span<Range>.Enumerator _enumerator;
由于它是
readonly
,因此 MoveNext
的代码正在执行防御性访问时复制,这是正确的,以防止对底层结构进行任何更改。
只需删除
readonly
即可使用
private Span<Range>.Enumerator _enumerator;