c# - 有没有办法将这个查询重写为更“LINQ-ier”?
问题描述
以下将- 分隔的/
URL 路径解析为键值对字典:
private Dictionary<string, string> ParsePathParameters(string path)
{
var parameters = new Dictionary<string, string>();
if (string.IsNullOrEmpty(path))
{
return parameters;
}
var pathSegments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
for (var i = pathSegments.Length - 1; i >= pathSegments.Length % 2; i -= 2)
{
parameters.Add(pathSegments[i - 1], pathSegments[i]);
}
return parameters;
}
输入格式是[/preamble][/key1/value1][/key2/value2]...[/keyN/valueN]
这样的,例如,给定输入“/foo/1/bar/Thing”或“/slug/foo/1/bar/Thing”,输出将是:
Dictionary<string, string>
{
{ "foo", "1" },
{ "bar", "Thing" },
}
这段代码是好代码;简单,不言自明,快速。但是,因为我喜欢挑战,所以我决定用 LINQ 重写它:
private Dictionary<string, string> ParsePathParameters(string path)
{
if (string.IsNullOrEmpty(path))
{
return new Dictionary<string, string>();
}
var pathSegments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
var skip = pathSegments.Length % 2;
return pathSegments.Skip(skip)
.Where((_, i) => i % 2 == 0)
.Select((_, i) => i * 2)
.ToDictionary(i => pathSegments[i + skip], i => pathSegments[i + skip + 1]);
}
这行得通,但感觉绝对不是最佳的,可能是因为它也感觉不是使用 LINQ 实现这一目标的“正确”方式。谁能建议是否可以以更“类似于 LINQ”的方式编写此代码,如果可以,请在这方面给我一些指示?
解决方案
我会这样写:
private Dictionary<string, string> ParsePathParameters(string path)
{
return GetSegmentPairs().ToDictionary(x => x.k, x => x.v);
IEnumerable<(string k, string v)> GetSegmentPairs()
{
var segments = path?.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
?? new string[0];
for (int i = 0, l = segments.Length; i < l; i += 2)
yield return (segments[i+0], segments[i+1]);
}
}
不要低估局部函数和生成器的威力。当您需要创建难以编写为直接 linq 查询的序列时,生成器非常有用。然后可以在 linq 查询中使用这些生成器。对于这种特殊情况,对于相当琐碎的查询甚至可能不需要,但对于更复杂的查询,它是无价的。但这是一种你应该学会更频繁地使用的模式。
如果使用 C# 8,我会养成在适当情况下使用跨度/内存和切片的习惯。
private Dictionary<string, string> ParsePathParameters(string path)
{
return GetSegments().ToDictionary(x => x.Span[0], x => x.Span[1]);
IEnumerable<System.Memory<string>> GetSegments()
{
var segments = path?.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
for (int i = 0, l = segments.Length; i < l; i += 2)
yield return segments[^i..i+1];
}
}
否则,如果您使用的是 MoreLINQ,则可以使用Pairwise()
withTakeEvery()
方法有效地执行与上述方法相同的操作GetSegmentPairs()
。
private Dictionary<string, string> ParsePathParameters(string path) =>
(path?.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
?? Enumerable.Empty<string>())
.Pairwise(ValueTuple.Create)
.TakeEvery(2) // pairwise produces overlapping pairs so take every other
.ToDictionary(x => x[0], x => x[1]);
推荐阅读
- .net-core - 在 azure devops 版本上安装并运行自定义 dotnet 工具
- reactjs - Onchange 字段数据正在成为 [object object] 反应
- javascript - React-Apollo:在上下文中找不到“客户端”或作为选项传入
- android - 有没有办法可视化使用 Android Studio 开发的 Flutter 应用程序中的特定 UI 元素?
- git - gitpython 获取带有分离头的最后一次提交
- r - R中的映射:无法将文本框移出我的地图
- r - R如何滞后4000列50次
- excel - 如何将范围从一个工作表剪切到粘贴到第二个并确保将来的行转到下一个空白行?
- python - 数组的所有可能组合,其中数组的每个元素都有 4 个可能的数字,python
- python - 问题 ValueError: x 和 y 必须是相同的大小