Join with Where 子句的查询和方法 (lambda) 语法的等效性

Equivalence of query and method (lambda) syntax of a Join with Where clause

我的简化 LINQ Join 加上两个表的 Where 如下所示:

var join = context.Foo
  .Join(context.Bar,
    foo => new { foo.Year, foo.Month },
    bar => new { bar.Year, bar.Month },
    (foo, bar) => new { foo.Name, bar.Owner, foo.Year })
  .Where(anon => anon.Year == 2015).ToList();

或者我可以使用以下我希望等效的语法:

var joinQuery = from foo in context.Foo
                join bar in context.Bar
                on new { foo.Year, foo.Month } equals new { bar.Year, bar.Month }
                where foo.Year == 2015
                select new { foo.Name, bar.Owner };
var join = joinQuery.ToList();

我想到并想知道的一个区别是命令的顺序。在 lambda-syntax 连接中,我将 foo.Year 属性 添加到我的匿名 return 类型中,这样我就可以在之后进行过滤,而在其他查询中我仍然可以使用 foobar 如果我愿意的话)在 where 子句中。如果我不想或不需要,我不需要将字段 foo.Year 添加到我的 return 类型。

不幸的是,我没有 ReSharper 或任何类似的东西可以将较低的语句转换为 lambda 语句以便我进行比较。

我实际上可以做的(并使上层语句在结构上与下层语句更相似)是在第一个 Where(..)ToList() 之间添加以下行:

.Select(anon => new { /* the properties I want */ })

但是与第二条语句相比,这不只是添加了 "one more" 匿名类型创建,还是我弄错了?

简而言之: 与第二条语句等效的 Join 语法是什么?或者第一个加上添加的 Select 真的是等价的,也就是说,joinQuery 是否在内部产生相同的代码?

对于 简而言之: 部分问题:答案是肯定的。
这是 resharpers 结果

var joinQuery = context.Foo.Join(context.Bar, foo => new
{
    foo.Year,
    foo.Month
}, bar => new
{
    bar.Year,
    bar.Month
}, (foo, bar) => new
{
    foo,
    bar
}).Where(@t => @t.foo.Year == 2015).Select(@t => new
{
    @t.foo.Name,
    @t.bar.Owner
});

在一般情况下,您不能总是完全在查询理解语法和 lambda 语法之间转换,就像编译器所做的那样。这是由于使用了 transparent identifiers。但是您可以解决这个问题并生成 语义等价的 lambda 语句。这就是 ReSharper 所做的。

无论如何,在你的情况下,你可以添加:

.Select(anon => new { /* the properties I want */ })

这将每行实例化一个匿名类型,但不会是 "one more",所以不用担心:the表达式被转换为 SQL,因此 join 中的 new { foo.Year, foo.Month } 语句并没有真正实例化这些对象,它们只是被转换为 SQL。只有最后一个 select 将同时用于 SQL SELECT 列表,并在检索到行后用于对象水化。

But doesn't this just add "one more" anonymous type creation compared to the 2nd statement, or am I mistaken here?

正如 的回答所示:在这种情况下,当您使用理解语法时,这就是编译器正在做的事情。通过在您的单一匿名类型中包含年份,您可以稍微减少开销。

但是:

  • 匿名类型非常轻量级:泛型的使用,以及在引用类型上实例化的泛型共享实现意味着代码很少(看看反编译器中的程序集)。
  • 虽然创建了大量实例,但在大多数情况下,它们会在第 0 代中被清理,因为它们几乎会立即被释放。
  • C# 编译器和 JIT 的优化器在这里有很多用处。很可能采取了捷径(但您需要从 运行 进程中读取 x86/x64 程序集才能看到)。

记住前两个 rules of optimisation:除非您可以显示 – 来自实际测试数据的分析器数据 – 您的性能问题集中在易于维护的清晰代码上。

另一种选择是将 where 方法移到 join 方法之前,然后将年份从匿名 class:

中删除
var join = context.Foo
    .Where(foo => foo.Year == 2015)
    .Join(context.Bar,
        foo => new { foo.Year, foo.Month },
        bar => new { bar.Year, bar.Month },
        (foo, bar) => new { foo.Name, bar.Owner })
    .ToList();

但总的来说,其他答案都是正确的,区别不大,编译器可以为您处理细节。