You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中如何实现ReadOnlySpan<T>的Split功能?求可行方案

实现ReadOnlySpan/Span的Split功能(无堆分配/支持未知片段数量)

针对你提出的需求,这里提供几种无堆分配、适配未知片段数量的实现方案,同时避开原生IndexOf的限制:

方案1:回调式处理(推荐,适配任意片段数量)

直接遍历Span,找到分隔符就调用回调处理当前片段,全程无需存储所有片段,完全在栈上操作,完美解决未知片段数量的问题。同时自定义查找逻辑,替代原生IndexOf:

public static void Split<T>(this ReadOnlySpan<T> span, T separator, Action<ReadOnlySpan<T>> action) where T : IEquatable<T>
{
    int start = 0;
    int length = span.Length;
    
    while (start < length)
    {
        // 自定义从指定位置查找分隔符的逻辑
        int separatorIndex = -1;
        for (int i = start; i < length; i++)
        {
            if (span[i].Equals(separator))
            {
                separatorIndex = i;
                break;
            }
        }

        if (separatorIndex == -1)
        {
            // 处理最后一段
            action(span.Slice(start));
            break;
        }

        // 处理当前片段
        action(span.Slice(start, separatorIndex - start));
        start = separatorIndex + 1;
    }
}

使用示例:

ReadOnlySpan<char> text = "hello,world,foo,bar".AsSpan();
text.Split(',', segment => 
{
    // 按需处理每个片段,比如打印或业务逻辑
    Console.WriteLine(segment.ToString());
});

方案优势

  • 完全无堆分配,所有操作在栈上完成
  • 不需要预先知晓片段数量
  • 自定义查找逻辑,避开原生IndexOf的功能限制

方案2:自定义ref struct枚举器(支持foreach遍历)

如果习惯用foreach遍历片段,可以实现一个ref struct枚举器(因ref struct无法实现接口,模拟枚举器行为),既能保持遍历习惯,又不会产生堆分配:

public ref struct SpanSplitEnumerator<T> where T : IEquatable<T>
{
    private readonly ReadOnlySpan<T> _source;
    private readonly T _separator;
    private int _currentStart;
    private int _currentEnd;

    public SpanSplitEnumerator(ReadOnlySpan<T> source, T separator)
    {
        _source = source;
        _separator = separator;
        _currentStart = 0;
        _currentEnd = -1;
    }

    public ReadOnlySpan<T> Current => _source.Slice(_currentStart, _currentEnd - _currentStart);

    public bool MoveNext()
    {
        _currentStart = _currentEnd + 1;
        if (_currentStart >= _source.Length)
            return false;

        // 自定义查找分隔符
        _currentEnd = -1;
        for (int i = _currentStart; i < _source.Length; i++)
        {
            if (_source[i].Equals(_separator))
            {
                _currentEnd = i;
                break;
            }
        }

        if (_currentEnd == -1)
            _currentEnd = _source.Length;

        return true;
    }

    public SpanSplitEnumerator<T> GetEnumerator() => this;
}

// 扩展方法封装
public static SpanSplitEnumerator<T> Split<T>(this ReadOnlySpan<T> span, T separator) where T : IEquatable<T>
{
    return new SpanSplitEnumerator<T>(span, separator);
}

使用示例:

ReadOnlySpan<char> text = "hello,world,foo,bar".AsSpan();
foreach (var segment in text.Split(','))
{
    Console.WriteLine(segment.ToString());
}

方案优势

  • 符合C#遍历习惯,代码可读性高
  • 枚举器为ref struct,全程栈上分配,无堆内存开销
  • 支持未知片段数量

方案3:栈分配存储片段(仅适用于可预估最大数量的场景)

如果能预估片段的最大数量,可以用stackalloc分配一个存储ReadOnlySpan<T>的Span,同样无堆分配,但无法预估数量时不建议使用(可能导致栈溢出):

public static int Split<T>(this ReadOnlySpan<T> span, T separator, Span<ReadOnlySpan<T>> destination) where T : IEquatable<T>
{
    int count = 0;
    int start = 0;
    int length = span.Length;

    while (start < length && count < destination.Length)
    {
        int separatorIndex = -1;
        for (int i = start; i < length; i++)
        {
            if (span[i].Equals(separator))
            {
                separatorIndex = i;
                break;
            }
        }

        if (separatorIndex == -1)
        {
            destination[count++] = span.Slice(start);
            break;
        }

        destination[count++] = span.Slice(start, separatorIndex - start);
        start = separatorIndex + 1;
    }

    return count;
}

使用示例:

ReadOnlySpan<char> text = "hello,world,foo,bar".AsSpan();
// 预估最多5个片段,栈分配存储容器
var segments = stackalloc ReadOnlySpan<char>[5];
int segmentCount = text.Split(',', segments);

for (int i = 0; i < segmentCount; i++)
{
    Console.WriteLine(segments[i].ToString());
}

关键说明

  1. 避开原生IndexOf的原因:我们自行实现了从指定位置查找分隔符的逻辑,完全可控,解决了原生IndexOf不支持startIndex的限制。
  2. 不返回堆集合的原因:ReadOnlySpan<T>是ref struct,无法存储在堆分配的集合(如List<ReadOnlySpan<T>>或数组)中,以上方案均通过栈操作或即时处理的方式规避了这一限制。

内容的提问来源于stack exchange,提问作者user22782688

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 17:40:25