繁体   English   中英

OrderBy Linq的奇怪行为

[英]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已更改,因为您更改了该功能。

延迟执行被发明的原因是因为通常您不需要枚举序列的所有元素。 在下列情况下,如果您创建完整的序列,那将是处理时间的浪费:

  • 我只想要第一个元素,
  • 我想跳过3个元素,然后取两个元素,
  • 我想要第一个元素的值为x
  • 我想知道序列是否包含任何元素

当然,只要您要求第一个元素,就需要创建完整序列的函数:

  • 如果您想要排序序列中的第一个,则必须对所有元素进行排序以找到第一个元素。
  • 如果你想要一组元素的第一个元素,其中组中的所有元素都具有相同的属性值X(Enumerable.GroupBy)

根据经验,明智的做法是尽可能长时间地将所有序列保持为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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM