在Web开发中,我们常常会遇到需要管理来自各种来源(例如HTTP标头、查询字符串、设置值等)的字符串集合的情况。妥善管理这些字符串集合不仅可以减少出现漏洞的几率,还能提升应用程序的性能。ASP.NET Core提供了一种特殊的只读结构体——StringValues,它旨在高效地处理多个字符串值,使用单个内部对象来表示空值、单个字符串或多个字符串。让我们深入了解一下StringValues。
提示:以下文章以管理HTTP标头字符串集合为例,但来自其他来源的集合与之类似。
传统方法 在管理字符串集合时,我们可能会想到使用数组来为每个标头键存储多个值。这种方法看似简单易懂,代码也很直观,但它会带来性能问题以及字符串管理方面的复杂性。当我们使用数组来存储标头值时,会遇到内存分配增加的问题。即便只是存储单个值(其实没必要使用数组来存),也必须分配一个数组,这就导致了内存浪费。在高流量的Web应用程序中,这可能会引发严重的性能问题。
让我们来看一个示例。在程序中,我们添加一个HeaderManager类来存储HTTP标头。代码如下:
public class HeaderManager
{
private readonly Dictionary<string, string[]> _headers = new();
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
方法接受一个字符串键和一个可变长度的字符串值数组(使用params
关键字)。然后,我们使用TryGetValue
方法来检查字典中是否包含指定的键。如果键存在,就会创建一个新数组,将现有值和新值合并,并用合并后的数组更新字典。
传统方法使用Dictionary<string, string[]>
来存储标头值。添加新值时,会分配一个新数组,复制现有值,然后追加新值。虽然这种方法简单,但由于频繁调整数组大小以及内存分配增加,会导致性能问题。
使用NameValueCollection NameValueCollection允许我们在单个键下存储多个值。让我们修改一下之前的代码:
public class HeaderManager
{
private readonly NameValueCollection _headers = new();
public void AddHeader(string key, params string[] values)
{
foreach (var value in values)
{
_headers.Add(key, value);
}
}
}
在这段代码中,我们声明了一个类型为NameValueCollection
的字段_headers
。在AddHeader
方法中,我们遍历数组,并将值添加到指定键下的集合中。NameValueCollection
简化了标头管理,但由于它内部使用数组,所以也需要额外的内存分配。
使用StringValues ASP.NET Core中的许多核心组件和中间件都使用
StringValues
来管理字符串集合。StringValues
是一个结构体(值类型),由于值类型存储在栈上,它们的分配和释放比堆分配的对象更快,从而减少了内存分配以及垃圾回收的开销。StringValues
对象可以存储空值、单个字符串或字符串数组。通过使用单个对象来存储值,有助于减少内存分配并提升应用程序性能。让我们来探究一下如何使用它。
3.1 安装 要使用StringValues
,需要安装Microsoft.Extensions.Primitives
包。运行以下命令:
dotnet add package Microsoft.Extensions.Primitives
3.2 使用方法
使用单个字符串初始化
StringValues
:
StringValues singleValue = new StringValues("value1");
在这个示例中,我们使用构造函数用单个字符串初始化一个StringValues
对象。这个构造函数能高效地存储单个字符串,而无需分配数组。
使用字符串数组初始化
StringValues
:
StringValues multipleValues = new StringValues(new[] { "value1", "value2" });
这里,我们使用数组构造函数用一个字符串数组来初始化一个StringValues
对象。在处理字符串集合时,这个构造函数很有用。
使用空值或
null
值初始化StringValues
:
StringValues emptyValue = new StringValues();
StringValues nullValue = new StringValues((string)null);
在这种情况下,我们使用空值和null
值构造函数。这些构造函数能处理没有提供值的情况,并确保StringValues
可以管理空值或null
输入。
3.3 隐式转换和逗号分隔的字符串表示形式 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}");
输出结果将是:
隐式转换单个字符串: value1
隐式转换多个字符串: value1,value2
逗号分隔的值: value1,value2
在上述代码中,当单个字符串隐式转换为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
来检查键是否存在。如果存在,我们使用StringValues.Concat
将新值连接到现有的StringValues
对象上;否则,我们创建一个新实例并将其添加到字典中。
在Web开发中,管理来自不同来源(如HTTP标头、查询字符串、设置值等)的字符串集合至关重要。像使用数组或NameValueCollection
来管理这些字符串的传统方法存在性能和内存管理方面的问题。数组的频繁调整大小会导致不必要的内存分配,并影响性能。
ASP.NET Core提供的StringValues
结构体是一种更高效的解决方案。StringValues
是一种只读值类型,它可以处理单个字符串、字符串数组以及空值。通过减少内存分配和垃圾回收需求,它提升了应用程序的性能。与传统方法相比,StringValues
能高效地处理字符串集合,并防止内存浪费。
使用StringValues
可以显著优化字符串集合管理,特别是在高流量的Web应用程序中,其优势会更加明显。
如果你喜欢我的文章,请给我一个赞!谢谢