多个linq"from"和变量可见性

Ale*_*i S 3 c# linq grouping

我试图理解为什么这个linq不编译(fundInvoices不可见):

Dictionary<Fund, IEnumerable<Invoice>> paidfundInvoices;
...
from fundInvoices in paidfundInvoices
from p in fundInvoices.Value
group p by p.VendorId into ps
select new Payment
{
    FundId = fundInvoices.Key.FundId, // ERROR here
    Value = ps.Sum(p => p.Amount)
}
Run Code Online (Sandbox Code Playgroud)

所以我继续将其更改为匿名类型用法,并且基金发票在这里神奇可见:

from fundInvoices in paidfundInvoices
select new
{
    Fund = fundInvoices.Key,
    Payments = from p in fundInvoices.Value
               group p by p.VendorId into ps
               select new Payment
               {
                   FundId = fundInvoices.Key.FundId,  // NO ERROR                                                                                                                                    
                   Value = ps.Sum(p => p.Amount)
               }
};
Run Code Online (Sandbox Code Playgroud)

但是这种匿名类型似乎是多余的,我没有使用它.我只需要一个支付对象的平面列表.但是我的代码只编译那种方式......

Eri*_*ert 14

我试图理解为什么这个linq不编译

理解的关键是阅读关于如何将查询降低到普通代码的规范部分.

让我们从您的查询开始:

from fundInvoices in paidfundInvoices
from p in fundInvoices.Value
group p by p.VendorId into ps
select new Payment {
  FundId = fundInvoices.Key.FundId, // ERROR here
  Value = ps.Sum(p => p.Amount)
}
Run Code Online (Sandbox Code Playgroud)

好的,第一步.规范中的规则是:

带有continuation的查询表达式from … into x …被翻译成from x in ( from … ) …

您的查询现在

from ps in (
  from fundInvoices in paidfundInvoices
  from p in fundInvoices.Value
  group p by p.VendorId)
select new Payment {
  FundId = fundInvoices.Key.FundId, // ERROR here
  Value = ps.Sum(p => p.Amount)
}
Run Code Online (Sandbox Code Playgroud)

现在应该清楚为什么fundInvoices不在select子句的范围内.fundInvoices是一个完全不同的查询的范围变量.

但是,如果不清楚,让我们继续前进.下一条规则是:

表单的查询表达式from x in e select v被翻译成( e ) . Select ( x => v )

您的查询现在

((from fundInvoices in paidfundInvoices
  from p in fundInvoices.Value
  group p by p.VendorId))
  .Select(ps => 
    new Payment {
      FundId = fundInvoices.Key.FundId,
      Value = ps.Sum(p => p.Amount)
    })
Run Code Online (Sandbox Code Playgroud)

现在我们可以翻译内部查询:

将带有第二个from子句的后跟不是select子句的查询表达式from x1 in e1 from x2 in e2 …转换为from * in ( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => new { x1 , x2 } ) …

*是一个"透明的标识符",我们将在一分钟内看到它的含义.

您的查询现在

((from * in (paidfundInvoices).SelectMany(
   fundInvoices => fundInvoices.Value, 
   (fundInvoices, p) => new {fundInvoices, p}) 
  group p by p.VendorId))
  .Select(ps => 
    new Payment {
      FundId = fundInvoices.Key.FundId,
      Value = ps.Sum(p => p.Amount)
    })
Run Code Online (Sandbox Code Playgroud)

最终规则:

表单的查询表达式from x in e group v by k被翻译成( e ) . GroupBy ( x => k , x => v )

所以那是

((((paidfundInvoices).SelectMany(
   fundInvoices => fundInvoices.Value, 
   (fundInvoices, p) => new {fundInvoices, p}))
  .GroupBy(* => p.VendorId, * => p)))
  .Select(ps => 
    new Payment {
      FundId = fundInvoices.Key.FundId,
      Value = ps.Sum(p => p.Amount)
    })
Run Code Online (Sandbox Code Playgroud)

*意思是"使一对在select-许多选入范围的匿名类型的成员的该Desugar和删除不必要的括号和我们有查询的最终形式:

paidfundInvoices
  .SelectMany(
    fundInvoices => fundInvoices.Value, 
    (fundInvoices, p) => new {fundInvoices, p})
  .GroupBy(pair => pair.p.VendorId, pair => pair.p)))
  .Select(ps => 
    new Payment {
      FundId = fundInvoices.Key.FundId,
      Value = ps.Sum(p => p.Amount)
    })
Run Code Online (Sandbox Code Playgroud)

现在应该非常清楚为什么fundInvoices不在延续范围内.这是在范围GroupBy得益于透明标识符脱糖,但它是不是在所有的范围中Select.

更一般地说:在LINQ范围中,通常从左边的声明流到右边的用法,但是有一些例外:into从范围中移除范围变量,而不是所有范围变量都在范围内的所有位置join,等等.阅读规范了解更多详情.