LINQ样式首选项[关闭]


21

我每天都在编程中经常使用LINQ。实际上,我很少(如果有的话)使用显式循环。但是,我发现我不再使用类似SQL的语法。我只是使用扩展功能。所以宁愿说:

from x in y select datatransform where filter 

我用:

x.Where(c => filter).Select(c => datatransform)

您更喜欢哪种LINQ风格,团队中的其他人对此感到满意?


5
可能值得注意的是,MS官方立场是查询语法更可取。
R0MANARMY 2011年

1
最终没关系。重要的是代码是可以理解的。一种形式在一种情况下可能会更好,而另一种在另一种情况下会更好。因此,请使用当时适当的一种。
克里斯·

我相信您的第二个示例称为lambda语法,我95%的时间都在使用它。其余5%的人使用查询语法,这是在执行联接时,我正尝试过渡到lambda语法联接,但就像其他人指出的那样,它变得凌乱。
松饼人

Answers:


26

我发现不幸的是,Microsoft根据MSDN文档的立场是查询语法更可取,因为我从未使用过查询语法,而是一直使用LINQ方法语法。我喜欢能够激发我一心一意的查询。比较:

var products = from p in Products
               where p.StockOnHand == 0
               select p;

至:

var products = Products.Where(p => p.StockOnHand == 0);

更快,更少的线条,在我看来更干净。查询语法也不支持所有标准的LINQ运算符。我最近做过的一个示例查询看起来像这样:

var itemInfo = InventoryItems
    .Where(r => r.ItemInfo is GeneralMerchInfo)
    .Select(r => r.ItemInfo)
    .Cast<GeneralMerchInfo>()
    .FirstOrDefault(r => r.Xref == xref);

据我所知,使用查询语法(在可能的范围内)复制此查询将如下所示:

var itemInfo = (from r in InventoryItems
                where r.ItemInfo is GeneralMerchInfo
                select r.ItemInfo)
                .Cast<GeneralMerchInfo>()
                .FirstOrDefault(r => r.Xref == xref);

对我来说,它看起来不易读,并且您仍然需要知道如何使用方法语法。我个人真的很喜欢LINQ使得声明式样式成为可能,并在任何可能的情况下都使用它-有时对我不利。例如,使用方法语法,我可以执行以下操作:

// projects an InventoryItem collection with total stock on hand for each GSItem
inventoryItems = repository.GSItems
    .Select(gsItem => new InventoryItem() {
        GSItem = gsItem,
        StockOnHand = repository.InventoryItems
            .Where(inventoryItem => inventoryItem.GSItem.GSNumber == gsItem.GSNumber)
            .Sum(r => r.StockOnHand)
     });

我以为上面的代码对于没有良好文档的人来说很难理解,如果他们在LINQ中没有扎实的背景,他们可能还是不理解。尽管如此,方法语法仍具有一些强大的功能,可以快速(就代码行而言)设计查询以获取有关多个集合的聚合信息,否则这些集合将花费很多乏味的foreach循环。在这种情况下,方法语法对于从中获得的结果来说是非常紧凑的。尝试使用查询语法执行此操作可能很快就会变得笨拙。


您可以在select内进行转换,但不幸的是,您不能指定不采用LINQ方法来获取前X个记录。在您只需要一条记录并且必须将所有查询放在方括号中的地方,这尤其令人讨厌。
谢夫

2
仅作记录,您可以执行Select(x => x.ItemInfo).OfType <GeneralMerchInfo>()而不是Where()。Select()。Cast <>(),我相信它会更快(2n的大O而不是n * 2m(我认为)。但您完全正确,从可读性的角度来看,lambda语法要好得多。
Ed James

16

我发现函数语法更令人赏心悦目。唯一的例外是,如果我需要加入两个以上的集合。Join()很快就会发疯。


同意...我更喜欢扩展方法的外观和可读性,但加入时除外(如前所述)。组件供应商(例如Telerik)大量使用扩展方法。我正在考虑的示例是它们在ASP.NET MVC中的Rad控件。您需要非常熟练地使用扩展方法来使用/阅读那些扩展方法。
Catchops 2011年

来这样说。除非涉及联接,否则我通常使用lambda。一旦有了连接,LINQ语法就会更易读。
肖恩

10

添加另一个答案是否为时已晚?

我已经编写了大量的LINQ到对象的代码,并且我认为至少在该领域中,最好理解两种语法,以便使用任何使代码更简单的代码-并非总是点语法。

当然,有时候点句法是可行的方式-其他人提供了其中几种情况;但是,我认为理解能力是短暂的-如果您愿意,可以说唱不好。因此,我将提供一个样本,在该样本中我认为理解是有用的。

这是解决数字替换难题的解决方案:(使用LINQPad编写的解决方案,但可以在控制台应用程序中独立运行)

// NO
// NO
// NO
//+NO
//===
// OK

var solutions =
    from O in Enumerable.Range(1, 8) // 1-9
                    //.AsQueryable()
    from N in Enumerable.Range(1, 8) // 1-9
    where O != N
    let NO = 10 * N + O
    let product = 4 * NO
    where product < 100
    let K = product % 10
    where K != O && K != N && product / 10 == O
    select new { N, O, K };

foreach(var i in solutions)
{
    Console.WriteLine("N = {0}, O = {1}, K = {2}", i.N, i.O, i.K);
}

//Console.WriteLine("\nsolution expression tree\n" + solutions.Expression);

...输出:

N = 1,O = 6,K = 4

还不错,逻辑线性地流动,我们可以看到它提供了一个正确的解决方案。这个难题很容易手动解决:推理3>> N0,并且O> 4 * N意味着8> = O> =4。这意味着最多要手工测试10个案例(2为N-by- 5为O)。我已经迷路了-这个谜题仅供LINQ插图之用。

编译器转换

编译器做了很多工作,将其转换为等效的点语法。除了通常的第二和后续from子句变成SelectMany调用外,我们还有一些let子句变成Select带有投影的调用,这两个子句都使用了透明标识符。正如我将要展示的那样,必须在点语法中命名这些标识符,这会使该方法的可读性大大降低。

我有一个技巧可以揭露编译器将这段代码转换为点语法的作用。如果取消注释上面的两条注释行并再次运行它,将得到以下输出:

N = 1,O = 6,K = 4

解决方案表达式树System.Linq.Enumerable + d_ b8.SelectMany(O => Range(1,8),(O,N)=> new <> f _AnonymousType0 2(O = O, N = N)).Where(<>h__TransparentIdentifier0 => (<>h__TransparentIdentifier0.O != <>h__TransparentIdentifier0.N)).Select(<>h__TransparentIdentifier0 => new <>f__AnonymousType12(<> h_ TransparentIdentifier0 = <> h _TransparentIdentifier0,NO = ((10 * <> H_ TransparentIdentifier0.N)+ <>ħ _TransparentIdentifier0.O)))。选择(<> H_ TransparentIdentifier1 =>新<>˚F _AnonymousType2 2(<>h__TransparentIdentifier1 = <>h__TransparentIdentifier1, product = (4 * <>h__TransparentIdentifier1.NO))).Where(<>h__TransparentIdentifier2 => (<>h__TransparentIdentifier2.product < 100)).Select(<>h__TransparentIdentifier2 => new <>f__AnonymousType32(<> H_ TransparentIdentifier2 = <>ħ _TransparentIdentifier2,K =( <> h_ TransparentIdentifier2.product%10)))。Where(<> h _TransparentIdentifier3 =>((((<> h_ TransparentIdentifier3.K!= <> h _TransparentIdentifier3。<> h_ TransparentIdentifier2。<>h _TransparentIdentifier1。<> h_TransparentIdentifier0.O)AndAlso(<> h _TransparentIdentifier3.K!= <> h_ TransparentIdentifier3。<> h _TransparentIdentifier2。<> h_ TransparentIdentifier1。<> h _TransparentIdentifier0.N))AndAlso((<> h_ TransparentIdentifier3。产物/ 10)== <> H_ TransparentIdentifier3。<>ħ _TransparentIdentifier2。<> H_ TransparentIdentifier1。<>ħ _TransparentIdentifier0.O)))。选择(<> H_ TransparentIdentifier3 =>新<>˚F _AnonymousType4`3(N = < > h_ 透明标识符3 。<> h _TransparentIdentifier2。<> h_ 透明标识符1 。<> h _TransparentIdentifier0.N,O = <> h_ TransparentIdentifier3。<> h_TransparentIdentifier2。<> h_ TransparentIdentifier1。<> h _TransparentIdentifier0.O,K = <> h__TransparentIdentifier3.K))

将每个LINQ运算符放在新的一行上,将“无法说”的标识符转换为我们可以“说”的标识符,将匿名类型更改为它们熟悉的形式,并更改AndAlso表达式树的术语以&&暴露编译器所做的转换,以达到等效的目的用点语法:

var solutions = 
    Enumerable.Range(1,8) // from O in Enumerable.Range(1,8)
        .SelectMany(O => Enumerable.Range(1, 8), (O, N) => new { O = O, N = N }) // from N in Enumerable.Range(1,8)
        .Where(temp0 => temp0.O != temp0.N) // where O != N
        .Select(temp0 => new { temp0 = temp0, NO = 10 * temp0.N + temp0.O }) // let NO = 10 * N + O
        .Select(temp1 => new { temp1 = temp1, product = 4 * temp1.NO }) // let product = 4 * NO
        .Where(temp2 => temp2.product < 100) // where product < 100
        .Select(temp2 => new { temp2 = temp2, K = temp2.product % 10 }) // let K = product % 10
        .Where(temp3 => temp3.K != temp3.temp2.temp1.temp0.O && temp3.K != temp3.temp2.temp1.temp0.N && temp3.temp2.product / 10 == temp3.temp2.temp1.temp0.O)
        // where K != O && K != N && product / 10 == O
        .Select(temp3 => new { N = temp3.temp2.temp1.temp0.N, O = temp3.temp2.temp1.temp0.O, K = temp3.K });
        // select new { N, O, K };

foreach(var i in solutions)
{
    Console.WriteLine("N = {0}, O = {1}, K = {2}", i.N, i.O, i.K);
}

如果运行,可以验证再次输出:

N = 1,O = 6,K = 4

...但是你会写这样的代码吗?

我想答案是NONBHN(不仅是,但还是地狱!)-因为它太复杂了。当然,您可以想出一些比“ temp0” ..“ temp3”更有意义的标识符名称,但要点是,它们不会在代码中添加任何内容-它们不会使代码性能更好,它们不会为了使代码读起来更好,他们只会弄糟代码,而如果您是手工做的话,那么毫无疑问,您会花一两个时间弄乱它,然后再进行修改。另外,玩“名称游戏”对于找到有意义的标识符已经足够困难了,因此,我欢迎编译器在查询理解方面为我提供的名称游戏突破。

这个难题样品可能不是真实世界足以让你认真对待; 但是,在查询理解方面确实存在其他场景:

  • Joinand 的复杂性GroupJoin:查询理解join子句中范围变量的作用域将原本可能会以点语法编译的错误转换为理解语法中的编译时错误。
  • 每当编译器在理解转换中引入透明标识符时,理解就变得很有价值。这包括以下任何一项的使用:多个from子句,joinjoin..into子句和let子句。

我知道在我的家乡有不止一家工程师店,这些店禁止理解语法。我认为这很遗憾,因为理解语法只是一种工具和有用的工具。我认为这很像是说:“有些事情您可以用螺丝刀来做,而凿子则不能。因为您可以使用螺丝刀作为凿子,所以从国王的法令开始,凿子就被禁止了。”


-1:哇 OP正在寻找一些建议。你写了一本小说!您介意将其收紧一点吗?
Jim G.

8

我的建议是,当可以使用理解语法完成整个表达式时,请使用查询理解语法。也就是说,我希望:

var query = from c in customers orderby c.Name select c.Address;

var query = customers.OrderBy(c=>c.Name).Select(c=>c.Address);

但我宁愿

int count = customers.Where(c=>c.City == "London").Count();

int count = (from c in customers where c.City == "London" select c).Count();

我希望我们提出了一些语法,可以更好地将两者混合使用。就像是:

int count = from c in customers 
            where c.City == "London" 
            select c 
            continue with Count();

但是可惜我们没有。

但基本上,这是一个偏好问题。做一个对您和您的同事来说更好的选择。


3
或者,您可以考虑通过“引入解释变量”重构将理解与其他LINQ运算符调用分开。例如,var londonCustomers = from c in ...; int count = londonCustomers.Count();
devgeezer 2012年

3

类似SQL的方法是很好的开始。但是由于它的局限性(它仅支持您当前语言支持的那些构造),最终开发人员会采用扩展方法样式。

我想指出,在某些情况下,可以通过类似SQL的样式轻松实现。

您也可以在一个查询中同时使用这两种方式。


2

我倾向于使用非查询语法,除非我需要在查询中途定义一个变量,例如

from x in list
let y = x.DoExpensiveCalulation()
where y > 42
select y

但是我写非查询语法像

x.Where(c => filter)
 .Select(c => datatransform)

2

由于订购,我总是使用扩展功能。举个简单的例子-在SQL中,您首先编写了select-即使实际上,第一个执行的地方。当您使用扩展方法编写代码时,我会感觉更加掌控。我对所提供的产品有所了解,并按照发生的顺序进行编写。


我想您会发现,在“查询理解”语法中,页面上的顺序与操作发生的顺序相同。与SQL不同,LINQ不会将“选择”放在第一位。
埃里克·利珀特

1

我也喜欢扩展功能。

可能是因为它在我看来不太像语法上的飞跃。

感觉也更具可读性,尤其是在使用具有linq api的第三方框架时。


0

这是我遵循的启发式方法:

加入时,LINQ表达式优先于lambda。

我认为带有连接的lambda看起来很杂乱,很难阅读。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.