我们在开发 Web 项目时经常遇到需要管理各种来源的字符串集合(例如HTTP 标头、查询字符串、设置的值等)的情况。合理的管理这些字符串集合不仅可以减少出bug的几率,也能提高应用程序的性能。ASP.NET Core 为我们提供了一种特殊的只读结构体 StringValues
,它旨在高效处理多个字符串值,使用单个内部对象来表示null、一个或多个字符串。 下面我们来简单的探讨一下StringValues
。
Tip:以下文章以管理HTTP 标头的字符串集合为例来讲解,其他来源的字符串集合类似。
一、传统方式
对于字符串集合的管理,我们会想到使用数组来存储每个标头关键字的多个值。 这种方法看起来简单易懂,代码也容易编写,但是却会带来性能和字符串管理的复杂性问题。当我们使用数组存储标头值时,就会遇到内存分配增加的问题。 即使在不必要的情况下,存储单个值也需要分配一个数组,这样就出现内了存浪费,那么在大流量网络应用中就会出现很明显的性能问题。
用一个例子来讲解以下。在程序中我们添加一个 HeaderManager
类,这个类用来存储HTTP标头,代码如下:
public class HeaderManager
{
private readonly Dictionary<string, string[]> _headers = [];
public void AddHeader(string key, params string[] values)
{
if (_headers.TryGetValue(key, out var existingValues))
{
var newValues = new string[existingValues.Length + values.Length];
existingValues.CopyTo(newValues, 0);
values.CopyTo(newValues, existingValues.Length);
_headers[key] = newValues;
}
else
{
_headers[key] = values;
}
}
}
在上面的代码中,我们声明类型为 Dictionary<string, string[]>
的私有字段 _headers
,我们又定义了一个 AddHeader
方法,它接收一个字符串类型的键key
以及一个使用 params
关键字标记的数量可变的字符串值values
。 接着我们使用 TryGetValue
方法来检查字典 _headers
中是否已包含指定的键,如果键存在就会创建一个新数组将现有值和新值合并起来,最后我们用合并后的数组更新字典。
传统方式使用 Dictionary<string, string[]>
类型的变量来存储标头值,当添加新值时分配一个新的数组,然后复制现有值并添加新值。这种方法虽然简单明了,但频繁调整数组大小会导致内存分配增加从而造成性能问题。
二、使用 NameValueCollection
NameValueCollection
允许我们在单个键下存储多个值。我们来修改一下前一小节的代码:
public class HeaderManager
{
private readonly NameValueCollection _headers = [];
public void AddHeader(string key, params string[] values)
{
foreach (var value in values)
{
_headers.Add(key, value);
}
}
}
在代码中,我们声明一个类型为 NameValueCollection
的字段 _headers
,在AddHeader
方法中遍历 values
数组,并将其添加到具有指定键的 _headers 集合中。NameValueCollection
简化了标头管理,但由于内部要处理数组,因此也需要分配更多内存。
三、使用 StringValues
在 ASP.NET Core 中的许多核心组件和中间件都使用StringValues
来管理字符串集合。StringValues
是 struct
类型(值类型),值类型是存储在堆栈中的,因此它与堆分配相比值类型的分配和取消分配速度更快,这又减少了内存的分配,进而降低了对垃圾回收的需求。StringValues
对象可以存储null值、单个字符串和字符串数组。它使用单个对象来存储值,可帮助我们的项目减少内存分配,从而提高应用程序性能。 下面我们一起来看一下如何使用吧。
3.1 安装
要使用 StringValues
就要安装Primitives
软件包,在命令行输入如下代码即可安装:
dotnet add package Microsoft.Extensions.Primitives
3.2 使用
-
包含单个字符串的
StringValues
:StringValues singleValue = new StringValues("value1");
在上面代码中,我们使用了
StringValues
的构造函数传入字符串初始化StringValues
对象。这个构造函数创建的对象可以有效地存储单个字符串而不需分配数组。 -
让我们用字符串数组初始化
StringValues
对象:StringValues multipleValues = new StringValues(new[] { "value1", "value2" });
在上面代码中,我们使用
StringValues
的数组构造函数,用字符串数组进行初始化。 当我们需要处理字符串集合时就可以使用这个构造函数。 -
使用空值或 null 值初始化
StringValues
对象:StringValues emptyValue = new StringValues(); StringValues nullValue = new StringValues((string)null);
在上面代码中,我们使用空值和 null 值初始化
StringValues
构造函数。 这个构造函数可处理无值的情况,确保StringValues
能管理空或 null 输入。
3.3 StringValues 的隐式转换以及逗号分隔字符串表示法
StringValues
支持从单个字符串或字符串数组的隐式转换,方便我们初始化。当StringValues
包含多个字符串时,它可以将它们表示为单个逗号分隔的字符串,代码如下:
StringValues implicitSingle = "value1";
Console.WriteLine($"隐式转换单个字符串: {implicitSingle}");
StringValues implicitMultiple = new[] { "value1", "value2" };
Console.WriteLine($"隐式转换多个字符串集合 : {implicitMultiple}");
StringValues values = new StringValues(new[] { "value1", "value2" });
Console.WriteLine($"逗号分隔值: {values}");
输出结果如下:
在上面代码中,当单个字符串被隐式转换为 StringValues`` 时它会直接显示该字符串,对于字符数组会转换为
StringValues```对象,并以逗号分隔列表的形式显示。
3.4 StringValues 的使用
我们修改一开始编写的HeaderManager
类,将这个类修改为使用StringValues
实现管理字符串集合,代码如下:
public class HeaderManager
{
private readonly Dictionary<string, StringValues> _headers = new();
public void AddHeader(string key, params string[] values)
{
if (_headers.TryGetValue(key, out var existingValues))
{
_headers[key] = StringValues.Concat(existingValues, new StringValues(values));
}
else
{
_headers[key] = new StringValues(values);
}
}
}
在上面代码中,我们声明一个 类型为 Dictionary<string, StringValues>
的字段 _headers
。 然后使用TryGetValue
检查_headers
字典中是否已包含键,如果键存在就使用 StringValues.Concat
方法将新值连接到现有的 StringValues
对象上,否则创建一个新的 StringValues
实例并将其添加到字典中。
四、总结
在开发 Web 项目时,管理不同来源的字符串集合(如 HTTP 标头、查询字符串、设置值等)至关重要。传统方法使用数组或 NameValueCollection
来管理这些字符串,但这些方法存在性能和内存管理问题。特别是数组的频繁调整会导致不必要的内存分配,影响性能。
ASP.NET Core 提供了 StringValues
结构体,作为更高效的解决方案。StringValues
是一个只读的值类型,支持单个字符串、字符串数组以及空值(null)。它通过减少内存分配和垃圾回收需求,提升了应用程序性能。与传统方法相比,StringValues
能更有效地处理字符串集合,避免了内存浪费。
使用 StringValues
可以显著优化管理字符串集合的性能,特别是在高流量网络应用中,其优势更加明显。