.NET 中不存在公开可用的 StringPool 类,真正可用的字符串驻留方案只有 string.Intern(全局不可释放)和 ConcurrentDictionary.GetOrAdd(手动可控),其余均为误称或已废弃。

StringPool 不是 .NET 原生类型,也不是 Community Toolkit 中的类 —— 它根本不存在于官方库中。你看到的 StringPool 多数是开发者自定义的字符串对象池封装,或对 string.Intern 的误称。真正在 .NET 6+ 中提供轻量级、线程安全、可配置字符串驻留能力的是 System.Runtime.CompilerServices.StringPool,但它仅限内部使用(internal),无法直接调用。
为什么搜不到 StringPool 类?
社区文章里写的 StringPool 通常有三种情况:
- 把
string.Intern和string.IsInterned包装成一个“池”类,起名StringPool; - 用
ConcurrentDictionary<string string></string>手写一个简易驻留缓存,也叫StringPool; - 混淆了
Microsoft.Toolkit.HighPerformance.StringPool(旧版 WinUI Toolkit 中曾短暂存在,已废弃)和当前CommunityToolkit.HighPerformance(.NET 6+,不含StringPool)。
截至 2026 年,CommunityToolkit.HighPerformance NuGet 包(v8.2+)**没有 StringPool 类型**,也没有字符串驻留相关 API。它的重点在 Span<char></char> / ReadOnlySpan<byte></byte> 高效操作,比如 Utf8Parser、Base64 编解码等。
真正可用的字符串驻留方案只有两个
如果你的目标是减少重复字符串内存分配(尤其在日志、JSON 解析、HTTP header 处理等场景),请直接用以下两种方式:
-
string.Intern:将字符串加入 CLR 全局驻留池,返回池中已有引用(若存在)或新加入后返回。适用于**生命周期长、值高度重复、且确定不会泄露敏感内容**的字符串(如协议名、状态码、固定字段名); -
ConcurrentDictionary<string string>.GetOrAdd</string>:手动维护一个弱引用或强引用字典池。更可控,支持清理、限容、区分作用域,但需自行管理线程安全与内存泄漏风险。
示例(安全的手动池):
private static readonly ConcurrentDictionary<string, string> _stringPool =
new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
public static string GetPooledString(string input)
{
if (string.IsNullOrEmpty(input)) return input;
return _stringPool.GetOrAdd(input, s => s); // 强引用,不自动释放
}
⚠️ 注意:string.Intern 一旦加入就**永不释放**(直到 AppDomain 卸载,.NET Core/5+ 中为进程生命周期),滥用会导致内存持续增长 —— 比如把用户输入、时间戳、GUID 全部 Intern,后果严重。
StringBuilder 重用比 StringPool 更常用、更有效
多数性能瓶颈不在“相同字符串重复分配”,而在“频繁拼接生成新字符串”。这时候,StringBuilder 重用 + Clear() 是性价比最高的优化手段:
- 避免每次循环都 new
StringBuilder; - 用
StringBuilderCache(System.Text内部类,非公开)思路,或直接用System.Text.StringBuilder的静态池(.NET 7+ 中StringBuilder构造函数已内置缓存逻辑,但仅限短生命周期小容量); - 更稳妥的做法仍是线程局部
StringBuilder实例 +Clear(),配合ArrayPool<char></char>控制底层 buffer 复用。
例如:
private static readonly ThreadLocal<StringBuilder> _builder =
new ThreadLocal<StringBuilder>(() => new StringBuilder(256));
public static string FormatMessage(string prefix, int id)
{
var sb = _builder.Value;
sb.Clear();
sb.Append(prefix).Append(": ").Append(id);
return sb.ToString();
}
真正需要字符串驻留的场景极少;而一上来就迷信“StringPool”却忽略 StringBuilder 生命周期、Span 零分配解析、或误用 Intern 锁死内存,反而会让性能更差。驻留不是银弹,它只解决“值完全相同且长期存活”的那一小撮字符串 —— 其余大部分时候,你该关心的是怎么别生成它们。



















