有什么办法可以重写此查询以使其更“ LINQ-ier”?

时间:2019-03-04 05:37:29

标签: c# linq

以下内容将由/分隔的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”的方式编写此代码,如果可以,请给我一些有关这方面的指针?

1 个答案:

答案 0 :(得分:5)

我会这样写:

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,我会习惯在适当的地方使用span / memory和slices。

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()TakeEvery()方法一起使用,以有效地执行与上述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]);