[英]Strange behaviour of OrderBy Linq
我有一个使用OrderBy()
Linq函数排序的列表,它返回一个IOrderedEnumerable
。
var testList = myList.OrderBy(obj => obj.ParamName);
ParamName是一个可以保存整数和字符串的对象。 上面的orderBy基于整数值对列表进行排序。 现在我在testList上运行foreach并根据其整数值将ParamName属性更改为某个字符串,如下所示,
using (var sequenceEnum = testList.GetEnumerator())
{
while (sequenceEnum.MoveNext())
{
sequenceEnum.Current.ParamName = GetStringForInteger(int.Parse(Convert.ToString(sequenceEnum.Current.ParamName)));
}
}
接下来发生的事情是上一循环之后列表中项目的顺序已被中断,并且已根据分配的字符串而不是初始排序对列表进行排序。
但是,当我将.ToList()
与.OrderBy()
子句一起使用时,将.OrderBy()
。
有谁能帮助我这里发生的事情?
样本输出图示:
编辑:我们都错了你的问题。 错误排序的原因是因为你正在比较“B”和“AA”,并期望AA在excel之后的B之后,当然不会按字母顺序发生。
在排序之前指定显式比较器或在执行排序之前将ParamName转换为Int。
Linq通常返回IEnumerable元素的原因是它具有惰性评估行为。 这意味着它将在您需要时评估结果,而不是在构建时评估结果。
调用ToList强制linq评估结果以生成预期列表。
TL; DR在执行linq查询并在获取结果之前更改源数据集时要非常小心。
原因是在EF中分离执行查询,这意味着在您通过.ToList()显式加载到内存之前,不会对DB进行实际查询。
正如你所说的那样.OrderBy()返回一个IOrderedEnumerable,它与foreach成语一起使用。 那么为什么不简化它做类似下面的事情呢?
foreach(var item in testList)
{
item.ParamName = GetStringForInteger(int.Parse(Convert.ToString(item.ParamName)));
}
正如大家在这里提到的,那是因为Linq被懒惰地评估了。 你可以在这里阅读更多内容: https : //blogs.msdn.microsoft.com/ericwhite/2006/10/04/lazy-evaluation-and-in-contrast-eager-evaluation/
你想做的可能是这样的:
var testList = myList.OrderBy(obj => obj.ParamName).Select(obj =>
{
obj.ParamName = GetStringForInteger(int.Parse(Convert.ToString(obj.ParamName)));
return obj;
});
IEnumerable对象本身并不表示对象序列,它表示根据请求将序列的第一个元素作为“当前元素”提供给您的算法,并为您提供当前元素之后的下一个元素。
当LINQ的发明,它决定LINQ使用延迟执行的,经常被称为懒评价的概念。 在使用延迟执行的可枚举函数的MSDN描述中,您将找到以下短语:
此方法通过使用延迟执行来实现。 立即返回值是一个对象,它存储执行操作所需的所有信息。 在通过直接调用其GetEnumerator方法或使用foreach枚举对象之前,不会执行此方法表示的查询。
如果创建IEnumerable,并更改IEnumerable对象所作用的对象,则此更改可能会影响结果。 如果函数作用的参数发生更改,则它与返回不同值的函数相当:
int x = 4;
int y = 5;
int MyFunction()
{
return x + y;
}
int a = MyFunction();
y = 7;
int b = MyFunction();
现在b不等于a。 与您的IEnumerable类似:
List<...> myList = CreateMySequence()
var IEnumerable<...> myOrder = myList.OrderBy(...);
myOrder不包含结果,但就像一个可以计算结果的函数。 如果更改myOrder使用的其中一个参数,结果可能会更改:
myList.Add(someElement);
var myResult = myOrder.ToList();
myResult已更改,因为您更改了该功能。
延迟执行被发明的原因是因为通常您不需要枚举序列的所有元素。 在下列情况下,如果您创建完整的序列,那将是处理时间的浪费:
当然,只要您要求第一个元素,就需要创建完整序列的函数:
根据经验,明智的做法是尽可能长时间地将所有序列保持为IEnumerable,直到您需要结果,或者直到用于创建序列的源被更改为止。
当从数据库,文件和互联网获取数据时,后者非常重要:您必须在关闭连接之前创建序列。
以下不会奏效
using (var myDbContext = new MyDbContext)
{
return MyDbContext.Customers.Where(customer => customer.Age > 18);
}
在离开using语句时,在Disposed myDbContext之前不执行数据库查询。 因此,一旦您要求序列中的任何元素,您将获得异常。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.