我不确定该表达式是否符合您的想法。但在这里。让我们稍微重写一下:
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 }