C产品如何满足特定用户需求?
- 内容介绍
- 文章标签
- 相关推荐
本文共计993个文字,预计阅读时间需要4分钟。
直接使用 + 代码块:
为什么 Substring 会触发 GC?
每次调用 string.Substring(),CLR 都要分配一块新堆内存来拷贝字符数据,哪怕只取 3 个字符。10 万次调用可能多出几 MB 堆分配,GC 次数随之上升。
而 ReadOnlySpan<char></char> 是纯栈上视图:只存起始地址和长度,不复制内容。只要原字符串没被 GC 回收(比如是参数传入、常量或长生命周期对象),它就安全。
-
str.Substring(3, 5)→ 新建string对象,堆分配 -
str.AsSpan().Slice(3, 5)→ 零分配,仅构造一个轻量结构体 - 注意:
Slice(3, 5)是从索引 3 开始取 5 个字符,不是[3..5);C# 8+ 支持范围语法str.AsSpan()[3..8],语义更清晰
函数签名必须声明为 ReadOnlySpan<char></char>
如果工具方法接收 string,哪怕内部立刻转成 AsSpan(),也已经晚了——参数一进方法,隐式转换就触发了一次堆分配。
正确写法是把入口参数类型直接设为 ReadOnlySpan<char></char>:
public static bool TryParseHeader(ReadOnlySpan<char> input, out string key, out string value) { int colon = input.IndexOf(':'); if (colon == -1) { key = null; value = null; return false; } <pre class="brush:php;toolbar:false;">ReadOnlySpan<char> k = input.Slice(0, colon); ReadOnlySpan<char> v = input.Slice(colon + 1); // 仅在真正需要 string 时才 ToString(),且尽量避免 key = k.ToString(); // 这里才分配 value = v.ToString(); return true;
}
- 调用时传
data.AsSpan(),别传data - 如果下游 API 只认
string,优先考虑改用支持ReadOnlySpan<char></char>的重载(如int.TryParse(ReadOnlySpan<char>, out int)</char>) - 切忌在循环里反复调用
.ToString(),那是典型的“用了 Span 却白忙活”
切片越界怎么办?别靠 try/catch
Slice() 在 Release 模式下不做越界检查,start + length > span.Length 会直接抛 System.IndexOutOfRangeException。异常开销远高于预判。
- 用
Math.Min()截断长度:span.Slice(start, Math.Min(length, span.Length - start)) - 用范围语法更安全:
span[start..]或span[..length],底层自动 clamp - 对不可信输入(如网络包、日志行),先用
span.Length做长度校验,再切
stackalloc 字符串解析的边界场景
当你要从原始字节流(如 HTTP body)解析字符串,又不想走 Encoding.UTF8.GetString() 这种全量分配路径时,可结合 stackalloc 和 ReadOnlySpan<char></char>:
Span<byte> utf8Bytes = stackalloc byte[256]; // ... 填充 utf8Bytes ReadOnlySpan<char> chars = Encoding.UTF8.GetChars(utf8Bytes); // 返回 ReadOnlySpan<char>,零分配
-
stackalloc有长度限制(默认 ~1MB 栈空间),务必确认上限;超长需回退到Memory<byte></byte> -
GetChars()返回的是ReadOnlySpan<char></char>,可直接用于后续切片、查找等操作 - 不要对
stackalloc内存做跨方法传递——它随作用域结束自动释放,逃逸即悬空
真正容易被忽略的是生命周期耦合:Span 安全的前提,是它所指向的原始内存比 Span 本身活得更久。传参、异步、字段存储,都是让 Span “意外逃逸”的高危动作——这些地方一旦出错,不是编译报错,而是运行时崩溃或静默数据损坏。
本文共计993个文字,预计阅读时间需要4分钟。
直接使用 + 代码块:
为什么 Substring 会触发 GC?
每次调用 string.Substring(),CLR 都要分配一块新堆内存来拷贝字符数据,哪怕只取 3 个字符。10 万次调用可能多出几 MB 堆分配,GC 次数随之上升。
而 ReadOnlySpan<char></char> 是纯栈上视图:只存起始地址和长度,不复制内容。只要原字符串没被 GC 回收(比如是参数传入、常量或长生命周期对象),它就安全。
-
str.Substring(3, 5)→ 新建string对象,堆分配 -
str.AsSpan().Slice(3, 5)→ 零分配,仅构造一个轻量结构体 - 注意:
Slice(3, 5)是从索引 3 开始取 5 个字符,不是[3..5);C# 8+ 支持范围语法str.AsSpan()[3..8],语义更清晰
函数签名必须声明为 ReadOnlySpan<char></char>
如果工具方法接收 string,哪怕内部立刻转成 AsSpan(),也已经晚了——参数一进方法,隐式转换就触发了一次堆分配。
正确写法是把入口参数类型直接设为 ReadOnlySpan<char></char>:
public static bool TryParseHeader(ReadOnlySpan<char> input, out string key, out string value) { int colon = input.IndexOf(':'); if (colon == -1) { key = null; value = null; return false; } <pre class="brush:php;toolbar:false;">ReadOnlySpan<char> k = input.Slice(0, colon); ReadOnlySpan<char> v = input.Slice(colon + 1); // 仅在真正需要 string 时才 ToString(),且尽量避免 key = k.ToString(); // 这里才分配 value = v.ToString(); return true;
}
- 调用时传
data.AsSpan(),别传data - 如果下游 API 只认
string,优先考虑改用支持ReadOnlySpan<char></char>的重载(如int.TryParse(ReadOnlySpan<char>, out int)</char>) - 切忌在循环里反复调用
.ToString(),那是典型的“用了 Span 却白忙活”
切片越界怎么办?别靠 try/catch
Slice() 在 Release 模式下不做越界检查,start + length > span.Length 会直接抛 System.IndexOutOfRangeException。异常开销远高于预判。
- 用
Math.Min()截断长度:span.Slice(start, Math.Min(length, span.Length - start)) - 用范围语法更安全:
span[start..]或span[..length],底层自动 clamp - 对不可信输入(如网络包、日志行),先用
span.Length做长度校验,再切
stackalloc 字符串解析的边界场景
当你要从原始字节流(如 HTTP body)解析字符串,又不想走 Encoding.UTF8.GetString() 这种全量分配路径时,可结合 stackalloc 和 ReadOnlySpan<char></char>:
Span<byte> utf8Bytes = stackalloc byte[256]; // ... 填充 utf8Bytes ReadOnlySpan<char> chars = Encoding.UTF8.GetChars(utf8Bytes); // 返回 ReadOnlySpan<char>,零分配
-
stackalloc有长度限制(默认 ~1MB 栈空间),务必确认上限;超长需回退到Memory<byte></byte> -
GetChars()返回的是ReadOnlySpan<char></char>,可直接用于后续切片、查找等操作 - 不要对
stackalloc内存做跨方法传递——它随作用域结束自动释放,逃逸即悬空
真正容易被忽略的是生命周期耦合:Span 安全的前提,是它所指向的原始内存比 Span 本身活得更久。传参、异步、字段存储,都是让 Span “意外逃逸”的高危动作——这些地方一旦出错,不是编译报错,而是运行时崩溃或静默数据损坏。

