【问题标题】:c# Join and lambda expressionc# Join 和 lambda 表达式
【发布时间】:2015-09-20 09:35:39
【问题描述】:

我正在尝试理解以下代码行。 Sequence1 和 2 是两个字符串数组。该代码应该实现内部连接效果。有人可以帮忙解释如何阅读吗?即 x => x.gn2 是什么? 我知道 n1 => n1.Length 是加入条件。我正在努力使用 lambda 表达式。提前谢谢了!

var j = sequence1.GroupJoin ( sequence2 , 
n1 => n1.Length , n2 => n2.Length , (n1, gn2) => new { n1, gn2 })
.SelectMany (x => x.gn2,(x, n2) => new { x.n1, n2 });

【问题讨论】:

标签: c# linq select join lambda


【解决方案1】:

我不确定该表达式是否符合您的想法。但在这里。让我们稍微重写一下:

static void Foo1()
{
    string[] sequence1 = new[] { "12", "34", "567" };
    string[] sequence2 = new[] { "ab", "cd", "efg" };

    var result = sequence1.GroupJoin(sequence2,
    n1 => n1.Length, n2 => n2.Length, (n1, gn2) => new { n1, gn2 })
    .SelectMany(x => x.gn2, (x, n2) => new { x.n1, n2 });

    result.ToList().ForEach(Console.WriteLine);
}

现在用另一种等效的形式再次重写它:

static void Foo2()
{
    string[] sequence1 = new[] { "12", "34", "567" };
    string[] sequence2 = new[] { "ab", "cd", "efg" };

    var joinResult = sequence1.GroupJoin(
        sequence2,
        n1 => n1.Length,
        n2 => n2.Length,
        (n1, gn2) => new {n1, gn2});

    Console.WriteLine("joinResult: ");
    joinResult.ToList().ForEach(Console.WriteLine);

    var result = joinResult.SelectMany(
        x => x.gn2,
        (x, n2) => new { x.n1, n2 });

    Console.WriteLine("result: ");
    result.ToList().ForEach(Console.WriteLine);
}

现在让我们来看第一部分(GroupJoin):

    var joinResult = sequence1.GroupJoin(
        sequence2,
        n1 => n1.Length,
        n2 => n2.Length,
        (n1, gn2) => new {n1, gn2});

我们正在加入两个系列。请注意,GroupJoin 是在 sequence1 上调用的扩展方法。阅读GroupJoin的文档,我们看到sequence1是外序列,第一个参数sequence2是内序列。
第二个参数n1 => n1.Length是一个基于外部集合的每个元素生成该元素的key的方法。
第三个参数n2 => n2.Length是基于内部集合的每个元素生成该元素的key的方法。
GroupJoin 现在有足够的数据来匹配第一个序列的元素和第二个序列的元素。在我们的例子中,字符串是根据它们的长度来匹配的。第一个序列中长度为 2 的所有字符串都与第二个序列中长度相同的 2 字符串匹配。第一个序列中长度为 3 的所有字符串都与第二个序列中长度相同的 3 字符串匹配。对于字符串长度的任何值,依此类推。
最后一个参数(n1, gn2) => new {n1, gn2} 是一个方法,它基于外部序列中的一个元素(即sequence1)和来自sequence2 的所有匹配元素的集合将生成一些结果。在这种情况下,结果是具有两个字段的匿名类型:

  • 名为n1 的第一个字段是来自sequence1 的元素。
  • 名为gn2 的第二个字段是来自sequence2 的所有匹配元素的集合。

接下来是SelectMany

var result = joinResult.SelectMany(
    x => x.gn2,
    (x, n2) => new { x.n1, n2 });

SelectMany 是在joinResult 上调用的扩展方法。花点时间看看我的帖子末尾,我复制了应用程序的输出,看看joinResult 序列的样子。请注意,joinResult 中的每个元素 x 都是带有字段 {n1, gn2} 的匿名类型,其中 gn2 本身就是一个序列。

第一个参数x => x.gn2是一个用lambda形式写的委托。 SelectMany 将为输入序列joinResult 的每个元素调用此方法。 SelectMany 调用此方法,以便每次调用都有机会生成中间集合。请记住,joinResult 中的每个元素 x 都是带有字段 {n1, gn2} 的匿名类型,其中 gn2 本身就是一个序列。有了这个,lambda x => x.gn2 转换集合 x.gn2 中的每个元素 x。

现在SelectMany 基于输入序列的每个元素可以生成一个新的中间序列,它将继续处理该中间序列。为此,我们有第二个参数。

第二个参数(x, n2) => new { x.n1, n2 } 是另一个用 lambda 形式编写的委托。这个委托由SelectMany 为中间序列的每个元素调用,带有两个参数:

  • 第一个参数是输入序列中的当前元素。
  • 第二个参数是中间序列的连续元素。

此 lambda 将这两个参数转换为另一个具有两个字段的匿名类型:

  • 第一个字段名为n1。如果您遵循来自joinResult 集合中的字段n1 的数据流)。
  • 名为n2 的第二个字段是中间序列的当前元素。

这一切听起来非常复杂,但是如果您调试应用程序并在战略点上放置一些断点,就会变得清晰。

让我们用等价的形式再重写一次:

static void Foo3()
{
    string[] sequence1 = new[] { "12", "34", "567" };
    string[] sequence2 = new[] { "ab", "cd", "efg" };

    var joinResult = sequence1.GroupJoin(
        sequence2,
        element1 => GetKey1(element1),
        element2 => GetKey2(element2),
        (n1, gn2) =>
        {
            // place a breakpoint on the next line
            return new {n1, gn2};
        });

    Console.WriteLine("joinResult: ");
    joinResult.ToList().ForEach(Console.WriteLine);

    var result = joinResult.SelectMany(
        x =>
        {
            // place a breakpoint on the next line
            return x.gn2;
        },
        (x, n2) =>
        {
            // place a breakpoint on the next line
            return new {x.n1, n2};
        });

    Console.WriteLine("result: ");
    result.ToList().ForEach(Console.WriteLine);
}

private static int GetKey1(string element1)
{
    // place a breakpoint on the next line
    return element1.Length;
}

private static int GetKey2(string element2)
{
    // place a breakpoint on the next line
    return element2.Length;
}

我建议您运行最冗长的方法 Foo3 并在指定的位置放置断点。这将帮助您更详细地了解这一切是如何运作的。

最后,我必须说,这一切看起来如此复杂的一个原因是变量的命名方式。这是另一种形式,不像 Foo3 那样冗长,可能相当容易阅读:

static void Foo4()
{
    string[] sequence1 = new[] { "12", "34", "567" };
    string[] sequence2 = new[] { "ab", "cd", "efg" };

    var groupJoinResult = sequence1.GroupJoin(
        sequence2,
        elementFromSequence1 => elementFromSequence1.Length,
        elementFromSequence2 => elementFromSequence2.Length,
        (elementFromSequence1, matchingCollectionFromSequence2) => new { elementFromSequence1, matchingCollectionFromSequence2 });

    var result = groupJoinResult.SelectMany(
        inputElement => inputElement.matchingCollectionFromSequence2,
        (inputElement, elementFromMatchingCollection) => new { inputElement.elementFromSequence1, elementFromMatchingCollection });

    result.ToList().ForEach(Console.WriteLine);
}

注意:运行Foo3的输出为:

joinResult:
{ n1 = 12, gn2 = System.Linq.Lookup`2+Grouping[System.Int32,System.String] }
{ n1 = 34, gn2 = System.Linq.Lookup`2+Grouping[System.Int32,System.String] }
{ n1 = 567, gn2 = System.Linq.Lookup`2+Grouping[System.Int32,System.String] }
result:
{ n1 = 12, n2 = ab }
{ n1 = 12, n2 = cd }
{ n1 = 34, n2 = ab }
{ n1 = 34, n2 = cd }
{ n1 = 567, n2 = efg }

【讨论】:

  • 非常感谢!我还是不太明白这条线SelectMany(x => x.gn2, (x, n2) => new { x.n1, n2 });。你能详细说明一下吗?
  • 我更新了我的回复以涵盖 SelectMany。如果这有帮助,请不要忘记将答案标记为已接受。
猜你喜欢
  • 1970-01-01
  • 2011-06-19
  • 1970-01-01
  • 2016-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-29
  • 1970-01-01
相关资源
最近更新 更多