Linq是.NET的一个很棒的补充,我发现它在很多情况下都很好用,即使我刚开始学习如何使用Linq。
然而,在我对Linq所做的阅读中,我发现开发人员需要注意一些微妙的事情,这可能会导致麻烦。
我已经提到了一个明确的警告,我发现这是延迟执行的结果。
所以我想知道,对Linq来说,Linq的新手应该知道哪些其他注意事项?
答案 0 :(得分:5)
在foreach循环中构建查询
IEnumerable<char> query = "Not what you might expect";
foreach(char vowel in "aeiou")
{
query = query.Where(c => c != vowel);
}
上面的代码只是因为延迟执行而从字符串中删除了“u”。
要删除所有元音,您需要执行以下操作:
IEnumerable<char> query = "Not what you might expect";
foreach(char vowel in "aeiou")
{
char temp = vowel;
query = query.Where(c => c != temp);
}
答案 1 :(得分:3)
我认为LINQ相当稳固,并没有太多重要的警告。我遇到的几乎每一个“问题”都是延迟执行的结果,这不是一个真正的问题,而是一种不同的思维方式。
我遇到的最大问题 - 在性能分析方面,LINQ是一个游戏规则改变者(或者至少是一个规则弯曲者)。延迟执行可能使得有时分析应用程序变得更加困难,并且还可以以意想不到的方式显着地改变运行时性能特征。某些LINQ操作看起来几乎是神奇的,它们的速度有多快,而其他LINQ操作比我预期的要长得多 - 但从代码或分析器结果来看并不总是显而易见。
话虽如此,一般来说,延迟执行可以弥补手动编码例程减慢的情况。我更喜欢更简单,更清晰的代码到它替换的代码。
另外,我发现我使用LINQ to Objects越多,我就越需要重新思考我的设计并重新修改我的集合。
例如,在我开始经常使用linq对象之前,我从未意识到我经常暴露IList而不是IEnumerable。我现在完全理解为什么MS设计指南经常警告不要使用IList(例如,不要仅为Count属性返回IList等)。当我有使用IList的方法时,从linq查询中传递IEnumerable结果需要.ToList()或重新处理方法的API。
但是几乎总是值得重新思考 - 我发现许多地方传递了一个可枚举的并使用LINQ导致了巨大的性能。收益。如果您考虑延迟执行是很好的,并充分利用它。例如,使用.Take()将集合限制为前2个元素,如果所需要的那些在前linq上更具挑战性,并且大大加快了我的一些恶意循环。
答案 2 :(得分:1)
好问题。正如里德所指出的那样,他们都主要来自deferred execution(但与他不同的是,我觉得这是一个缺点。只是想到为什么不能通过记忆国家来推迟执行)。以下是几个示例 - 所有这些都是延迟执行问题的或多或少变体。
1)我懒得按时做某事
Linq仅在需要时执行。
一个常见的错误新手(我自己过去包括)make不知道延迟执行。例如,像
这样的东西 var p = listOfAMillionComplexItems.OrderBy(x => x.ComplexProperty);
快速运行,但实际排序在枚举列表之前没有完成,换句话说,在您需要执行结果之前,执行没有完成。要执行它,您需要以下内容:
foreach(var item in p)...
//or
p.Count();
//or
p.ToList();
//etc
将它们视为SQL查询。如果你有
var query = from i in otherValues where i > 5 select i;
认为它类似于写作
string query = "SELECT i FROM otherValues WHERE i > 5";
后者是否会调用db?不,你必须
Execute(query);
这与Linq一样。
2)我住在现在
对Linq表达式中的变量进行更改时要小心谨慎 以后。
为了安全起见,首先备份变量然后在查询中使用备份,如果变量可以在实际执行查询之前更改。
decimal minimumBalance = 500;
var customersOver500 = from c in customers
where c.Balance > minimumBalance
select c;
minimumBalance = 200;
var customersOver200 = from c in customers
where c.Balance > minimumBalance
select c;
int count1 = customersOver500.Count();
int count2 = customersOver200.Count();
假设我们有四个客户,余额如下:100,300,400和600. count1和count2是什么?他们都是3.&#34; customersOver500&#34;引用&#34; minimumBalance&#34;变量,但是在查询结果被迭代之前(通过for / each循环,ToList()调用或者甚至是&#34; Count()&#34;调用,都不会获得该值,如上所示) 。在使用该值处理查询时,minimumBalance的值已更改为200,因此两个LINQ查询都会生成相同的结果(余额超过200的客户)。
3)我的记忆力太弱,无法记住过去的贵重物品
与上述相同,背景略有不同。
或来自同一网站:
考虑一个使用LINQ-to-SQL获取客户列表的方法的简单示例:
public IEnumerable<Customer> GetCustomers()
{
using(var context = new DBContext())
{
return from c in context.Customers
where c.Balance > 2000
select c;
}
}
看起来非常无害 - 直到你得到一个&#34; ObjectDisposedException&#34;当您尝试枚举集合时。为什么?因为在尝试枚举结果之前,LINQ实际上并不执行查询。当此调用退出时,将丢弃DBContext类(公开Customers集合)。尝试枚举整个集合后,将引用DBContext.Customers类,并获得异常。
4)不要试着抓住我,我可能还是会溜走
如果没有明智地使用,try-catch对于声明来说毫无意义。
相反,全局异常处理在这里会更好。
try
{
wallet = bank.Select(c => Convert.ToInt32(""));
}
catch (Exception ex)
{
MessageBox.Show("Cannot convert bad int");
return;
}
foreach(int i in wallet)
//kaboom!
我们都没有收到正确的错误消息,也没有return
退出该功能。
5)我不仅没有不正常,而且我也没有从错误中吸取教训
每次枚举时都会执行Linq。所以不要重复使用Linq enumerables。
假设您从Linq表达式返回IQueryable
或IEnumerable
。现在枚举集合将获得执行语句,但只执行一次?不,每次都这样做。这在过去曾让我感到困惑。如果你有:
var p = listOfAMillionComplexItems.OrderBy(x => x.ComplexProperty);
MessageBox.Show(p.Count().ToString()); //long process.
MessageBox.Show(p.Count().ToString()); //long process still.
做得更好
int i = p.Count(); //store in a variable to access count
//or better
var list = p.ToList(); //and start using list
6)如果您不知道如何使用我,我可能会产生副作用!
与上述相同,只是为了说明如何重用Linq枚举可能导致不良行为。
确保你不进行副作用编程(因为在Linq中重新枚举更为常见)举个例子,
p = bag.Select((t, i) => {if(i == 1) MessageBox.Show("Started off"); return t;});
如果你列举两次,就会知道会发生什么不良事件。
7)警惕我在执行链接时执行
不仅仅是变量,即使链接的Linq函数也可以按照您通常的预期顺序执行(尽管行为是正确的)。不要想必要(一步一步),想想Linq怎么可能执行它。
例如,
var d = Enumerable.Range(1, 100);
var f = d.Select(t => new Person());
f = f.Concat(f);
f.Distinct().Count() ??
f
中不同人物的数量是多少?我猜100,不,但它是200.问题在于,当实际执行连接逻辑时,f
仍然是d.Select(t => new Person()
未执行。所以这有效地产生了
f = d.Select(t => new Person()).Concat(d.Select(t => new Person()));
然后有200个不同的成员。 Here's a link for the actual problem
8)嘿,实际上我们比你想象的更聪明。
本身并不是一个警告,但在很多情况下,Linq可以胜过你的命令式风格程序。所以在优化之前,再考虑一下,甚至是基准。
延迟执行基本上按需执行的原因使Linq比它看起来更有效率。迭代器块&#34;产生&#34;按照要求,一次一个项目,在不再需要时提供停止执行的能力。这是一个非常好的问题,详细说明:Order of LINQ extension methods does not affect performance?
9)我不打算收紧数字
滥用Linq会使代码效率低下,而且可读性也会降低。
对于数字运算算法,Linq不是正确的工具,特别是对于复杂度可以指数级扩展的大型数据集。有时只需要两个for循环就足够了。与LINQ to SQL相比,这同样适用于原始SQL。
10)雇用我做正确的工作
要求Linq注意您的正常业务是糟糕的编程选择,这与可读性相悖。
有些例如:
medicines.Any(p =>
{
Console.WriteLine(p);
return false;
});
对于可枚举的foreach。
或
medicines = medicines.Select(p =>
{
p.Id = 3;
return p;
});
只是糟糕的工具。
11)调试和分析可能是一场噩梦
很难跟随VS的Linq表达式发生的事情。
这不是完全不可能的,但是调试linq查询的任务与VS本身的非linq代码一样有效。由于延迟执行的性质,分析也变得更难。但是它不应该阻止任何人做一些简单的一两个衬垫!
一些警告或多或少都与延迟执行有关! A ditto question here。关于SO的一些相关阅读:
Examples on when not to use LINQ
Pros and Cons of LINQ (Language-Integrated Query)
What is the biggest mistake people make when starting to use LINQ?