c#Join和lambda表达式

时间:2015-09-20 09:35:39

标签: c# linq select join lambda

我试图理解以下代码行。 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 });

1 个答案:

答案 0 :(得分:3)

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

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是一个基于外部集合的每个元素生成该元素的键的方法 第三个参数n2 => n2.Length是一个基于内部集合的每个元素生成该元素的键的方法 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序列的样子。请注意,x中的每个元素joinResult都是一个匿名类型,其中包含字段{n1, gn2},其中gn2本身就是一个序列。

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

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

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

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

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

  • 第一个名为n1的字段。如果您从n1中的集合中跟踪来自字段joinResult的数据流。
  • 名为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 }