寻求可访问底层存储索引的ReadOnlyMemory<T>替代方案
关于ReadOnlyMemory替代类型及跨令牌切片的解决方案
核心问题的可行方案
1. 给令牌额外存储原始偏移量(推荐)
不用替换ReadOnlyMemory<T>,而是在你的令牌结构体中新增一个int OriginalStartIndex字段,记录该令牌在最原始源数据中的起始位置。分词时,每次从源切片出新的ReadOnlyMemory<char>时,同步计算并存储这个偏移量:
public struct Token { public ReadOnlyMemory<char> Value { get; } public int OriginalStartIndex { get; } public int Length => Value.Length; public Token(ReadOnlyMemory<char> value, int originalStartIndex) { Value = value; OriginalStartIndex = originalStartIndex; } }
分词器逻辑示例:
// 假设原始源是整个字符串的Memory<char> var originalSource = "原始文本数据".AsMemory(); int currentOffset = 0; // 切分第一个令牌 var firstTokenSlice = originalSource.Slice(0, 5); var firstToken = new Token(firstTokenSlice, currentOffset); currentOffset += firstTokenSlice.Length; // 切分后续令牌同理...
解析器需要跨令牌切片时,直接用起始令牌的OriginalStartIndex和结束令牌的OriginalStartIndex + 结束令牌.Length,从原始源中生成新切片:
// 从token1起始到token3结束的跨令牌切片 var crossSlice = originalSource.Slice(token1.OriginalStartIndex, token3.OriginalStartIndex + token3.Length - token1.OriginalStartIndex);
这个方案改动最小,对现有客户端完全透明——不需要修改他们使用Token.Value的逻辑,仅解析器需要用到新增的偏移量字段,调试和维护成本都很低。
2. 自定义带公开索引的Memory包装类型
如果确实需要替代ReadOnlyMemory<T>,可以实现一个轻量的包装结构,公开原始源、起始索引和长度:
public readonly struct TrackedReadOnlyMemory<T> { public readonly ReadOnlyMemory<T> Source; public readonly int StartIndex; public readonly int Length; public ReadOnlyMemory<T> Slice => Source.Slice(StartIndex, Length); public TrackedReadOnlyMemory(ReadOnlyMemory<T> source, int startIndex, int length) { Source = source; StartIndex = startIndex; Length = length; } // 支持隐式转换为ReadOnlyMemory<T>,兼容现有客户端 public static implicit operator ReadOnlyMemory<T>(TrackedReadOnlyMemory<T> tracked) => tracked.Slice; // 提供跨切片的合并方法 public TrackedReadOnlyMemory<T> MergeWith(TrackedReadOnlyMemory<T> other) { if (!ReferenceEquals(Source, other.Source)) throw new InvalidOperationException("无法合并不同源的切片"); var newStart = Math.Min(StartIndex, other.StartIndex); var newEnd = Math.Max(StartIndex + Length, other.StartIndex + other.Length); return new TrackedReadOnlyMemory<T>(Source, newStart, newEnd - newStart); } }
这个方案完全满足你的需求,但需要确保包装类型的行为和ReadOnlyMemory<T>一致,比如处理内存所有权、不同内存源(数组、NativeMemory等)的兼容性,维护成本略高。
3. 用MemoryMarshal获取底层索引(谨慎使用)
通过MemoryMarshal.TryGetArray可以提取ReadOnlyMemory<T>对应的数组段,从而拿到偏移量,但这个方法仅在底层存储是数组时有效,若源是其他类型(比如NativeMemory、MemoryManager实现)会失败,且属于非公开约定的用法,存在版本兼容性风险:
if (MemoryMarshal.TryGetArray(tokenValue, out ArraySegment<char> segment)) { // segment.Offset就是该切片在原始数组中的起始索引 int originalIndex = segment.Offset; } else { // 处理非数组源的情况,可能需要 fallback 到其他方案 }
不推荐在生产代码中长期使用,仅适合临时验证或特定场景。
内容的提问来源于stack exchange,提问作者Giles
相关产品推荐
相关产品推荐

