2

我有一个使用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()子句一起使用时,顺序会被保留。

谁能帮我这里发生了什么?

示例输出说明:

在此处输入图像描述

4

4 回答 4

9

编辑: 我们都把你的问题弄错了。它以错误方式排序的原因是因为您正在比较“B”和“AA”,并期望 AA 像在 excel 中一样在 B 之后,这当然不会按字母顺序发生。

在排序时指定一个显式比较器,或者在进行排序之前将 ParamName 转换为 Int。


Linq 通常返回 IEnumerable 元素的原因是它具有惰性求值行为。这意味着它将在您需要时评估结果,而不是在您构建它时。

调用 ToList 会强制 linq 评估结果以生成预期的列表。

TL;DR 在获取结果之前进行 linq 查询和更改源数据集时要非常小心。

于 2017-01-19T09:24:26.907 回答
1

原因是 EF 中查询的分离执行,这意味着直到您通过 .ToList() 将其显式加载到内存中之前,才会对 DB 进行实际查询。

正如您所说的那样,.OrderBy() 返回一个 IOrderedEnumerable,它与 foreach 习语一起使用。那么为什么不简化它做如下的事情呢?

foreach(var item in testList)
{
       item.ParamName = GetStringForInteger(int.Parse(Convert.ToString(item.ParamName)));
}
于 2017-01-19T09:41:52.030 回答
1

IEnumerable 对象本身并不表示对象序列,它表示根据请求为您提供序列的第一个元素作为“当前元素”所需的算法,并为您提供当前元素之后的下一个元素。

当 linq 被发明时,人们决定 linq 使用延迟执行的概念,通常称为惰性求值。在使用延迟执行的 Enumerable 函数的 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 之前不会执行数据库查询。因此,只要您要求序列中的任何元素,您就会得到一个例外。

于 2017-01-19T09:57:01.413 回答
0

正如这里的每个人都提到的,那是因为 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;
});
于 2017-01-19T09:48:17.540 回答