使用封装的Span<Range>.Enumerator

问题描述 投票:0回答:2

我正在用

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。

c# .net-core .net-8.0
2个回答
0
投票

这是因为结构是值类型+你的

_enumerator
readonly
结构。因此,当您调用
BasicEnumerator.MoveNext()
时,您将获得内部 _enumerator 结构体的
copy
,然后调用该副本的
MoveNext()
,然后丢弃该副本。原始结构保持不变,因此它的
Current
仍然是 -1。

要解决此问题,请删除只读限定符

//remove `readonly`
private /*readonly*/ Span<Range>.Enumerator _enumerator;

0
投票

错误就在这里

private readonly Span<Range>.Enumerator _enumerator;

由于它是

readonly
,因此
MoveNext
的代码正在执行防御性访问时复制,这是正确的,以防止对底层结构进行任何更改。

只需删除

readonly
即可使用

private Span<Range>.Enumerator _enumerator;

点网小提琴

© www.soinside.com 2019 - 2024. All rights reserved.