Linq order子句似乎在复制和丢失对象



我正在写一个Windows窗体程序。我对linq还是个新手。我想在内存中排序一个对象列表。对象属于Ctrl类(并表示屏幕上的控件)。它们的属性包括:CtrlID, Name, X, Y, TabIndex。对于许多控件,TabIndex可能为零,所以我想按TabIndex, Y, X排序,然后遍历它们,设置TabIndex从1开始,递增,所以它是连续的。

我使用的代码是:

int i;
Ctrl oCtrl = null;
Debug.WriteLine("Before");
for (i = 0; i < _ctrls.Count; i++)
{
oCtrl = _ctrls[i];
Debug.WriteLine(string.Format("{0},ID:{1}: Name:{2}, TabIndex:{3}, X:{4}, Y:{5} ", i.ToString(), oCtrl.CtrlID, oCtrl.Name, oCtrl.TabIndex.ToString(), oCtrl.X, oCtrl.Y));
}
var ctrls = from ctrl in _ctrls.Items 
orderby ctrl.TabIndex, ctrl.Y, ctrl.X 
select ctrl;
Debug.WriteLine("After");
for (i = 0; i < ctrls.Count(); i++)
{
oCtrl = ctrls.ElementAt(i);
Debug.WriteLine(string.Format("{0},ID:{1}: Name:{2}, TabIndex:{3}, X:{4}, Y:{5} ", i.ToString(), oCtrl.CtrlID, oCtrl.Name, oCtrl.TabIndex.ToString(), oCtrl.X, oCtrl.Y));
oCtrl.TabIndex = i + 1; // cos they're one-based
}

我在输出窗口得到的结果是:

0,ID:125: Name:cmd, TabIndex:0, X:124, Y:12 
1,ID:126: Name:chk, TabIndex:0, X:124, Y:36 
2,ID:127: Name:db, TabIndex:0, X:124, Y:60 
3,ID:128: Name:LABEL1, TabIndex:0, X:8, Y:64 
4,ID:129: Name:LABEL2, TabIndex:0, X:8, Y:88 
5,ID:130: Name:nf, TabIndex:0, X:124, Y:84 
6,ID:131: Name:ni, TabIndex:0, X:124, Y:108 
7,ID:132: Name:LABEL3, TabIndex:0, X:8, Y:112 
8,ID:133: Name:sc, TabIndex:0, X:124, Y:132 
9,ID:134: Name:LABEL4, TabIndex:0, X:8, Y:136 
10,ID:135: Name:sv, TabIndex:0, X:124, Y:156 
11,ID:136: Name:LABEL5, TabIndex:0, X:8, Y:160 
12,ID:137: Name:LABEL6, TabIndex:0, X:8, Y:292 
13,ID:138: Name:txt, TabIndex:0, X:124, Y:288 
14,ID:139: Name:LABEL7, TabIndex:0, X:8, Y:40 
15,ID:140: Name:LABEL8, TabIndex:0, X:8, Y:16 
After
0,ID:125: Name:cmd, TabIndex:0, X:124, Y:12 
1,ID:126: Name:chk, TabIndex:0, X:124, Y:36 
2,ID:127: Name:db, TabIndex:0, X:124, Y:60 
3,ID:130: Name:nf, TabIndex:0, X:124, Y:84 
4,ID:131: Name:ni, TabIndex:0, X:124, Y:108 
5,ID:133: Name:sc, TabIndex:0, X:124, Y:132 
6,ID:135: Name:sv, TabIndex:0, X:124, Y:156 
7,ID:138: Name:txt, TabIndex:0, X:124, Y:288 
8,ID:125: Name:cmd, TabIndex:1, X:124, Y:12 
9,ID:127: Name:db, TabIndex:3, X:124, Y:60 
10,ID:131: Name:ni, TabIndex:5, X:124, Y:108 
11,ID:135: Name:sv, TabIndex:7, X:124, Y:156 
12,ID:125: Name:cmd, TabIndex:9, X:124, Y:12 
13,ID:131: Name:ni, TabIndex:11, X:124, Y:108 
14,ID:125: Name:cmd, TabIndex:13, X:124, Y:12 
15,ID:125: Name:cmd, TabIndex:15, X:124, Y:12

所有名为"LABEL…"都不见了,还有一些是重复的。TabIndex显示的值为>0是由于我在打印输出后设置了它的值,但是因为一些对象被引用了两次,所以新值在第二次循环中显示。如果我注释掉order子句,使其成为一个直接的选择,那么我将以当前的顺序获得数据,没有重复或缺少项。

:

  1. 为什么我缺少对象?
  2. 为什么我有重复的对象?
  3. 我如何得到命令去做我所期望的?

感谢您提供的帮助。

我最初的评估是错误的,我一直在思考我遗嘱中非常有效的观点。然而,即使我指出的解决方案有效,这个特殊情况的根本原因似乎也不是突变本身。

你遇到的这个特殊问题的根本原因是使用ElementAt (https://referencesource.microsoft.com/#system.core/system/linq/Enumerable.cs,1231)

public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index) {
if (source == null) throw Error.ArgumentNull("source");
IList<TSource> list = source as IList<TSource>;
if (list != null) return list[index];
if (index < 0) throw Error.ArgumentOutOfRange("index");
using (IEnumerator<TSource> e = source.GetEnumerator()) {
while (true) {
if (!e.MoveNext()) throw Error.ArgumentOutOfRange("index");
if (index == 0) return e.Current;
index--;
}
}
}

似乎如果源不是IList<TSource>,则每次生成一个新的IEnumerator<TSource>,并进行迭代,直到到达元素。我相信如果有的话,你会使用索引。看来这就是为什么你的顺序变了。(关于IList vs IEnumerable的自我推销:https://stackoverflow.com/a/52450636/8695782)

最重要的是,这种访问元素的方式效率非常低,即使在这种情况下不引人注意。除了效率低下之外,在不可rewindable IEnumerables(即非列表支持的永久yield)的情况下,您还可能遇到其他麻烦。

如果你将循环改为:

Console.WriteLine("After");
var index = 1;
foreach (var ctrl in _ctrls)
{
oCtrl = ctrl;
oCtrl.TabIndex = index++;
Console.WriteLine(string.Format("Name:{0}, TabIndex:{1}", oCtrl.Name, oCtrl.TabIndex.ToString()));
}

那么你的代码应该工作得很好,因为我们只隐式地创建了一个IEnumerator<TSource>在写前一行。

[原始评估-(部分)错误]

正如mjwills在他的评论中漂亮地指出的那样,你的LINQ查询更像是一个食谱而不是一顿饭,所以当你迭代它时它可能会重新评估。在迭代任何类型的IEnumerable/IQueryable时改变对象,从长远来看通常会导致灾难。这就是为什么你会有重复的/丢失的项目。

3。在您的特定情况下,更改

orderby ctrl.TabIndex, ctrl.Y, ctrl.X 

orderby ctrl.Y, ctrl.X

将达到我认为期望的结果,因为TabIndex是你试图计算和更新的东西。

从我在代码中可以看出,它们都以TabIndex = 0开始。所以在这个特殊的例子中,只按X和Y排序意味着改变TabIndex不会影响排序。