C# Linq orderby子句似乎正在复制和丢失对象
我正在写一个Windows窗体程序。我对林克是相当陌生的。我想订购内存中的对象列表。这些对象属于Ctrl类(表示屏幕上的控件)。它们的属性包括:CtrlID、Name、X、Y、TabIndex。对于很多控件,TabIndex可能是零,所以我想按TabIndex,Y,X对它们排序,然后遍历它们,从1开始设置TabIndex,递增,所以它是连续的 我使用的代码如下:C# Linq orderby子句似乎正在复制和丢失对象,c#,linq,duplicates,C#,Linq,Duplicates,我正在写一个Windows窗体程序。我对林克是相当陌生的。我想订购内存中的对象列表。这些对象属于Ctrl类(表示屏幕上的控件)。它们的属性包括:CtrlID、Name、X、Y、TabIndex。对于很多控件,TabIndex可能是零,所以我想按TabIndex,Y,X对它们排序,然后遍历它们,从1开始设置TabIndex,递增,所以它是连续的 我使用的代码如下: int i; Ctrl oCtrl = null; Debu
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
}
所有名为“LABEL…”的控件都已丢失,其余部分已复制。TabIndex显示为>0的值是由于我在打印输出后设置了它的值,但由于某些对象被引用了两次,所以新值显示为第二次。如果我注释掉orderby子句,使其成为一个直接选择,那么我将按照数据当前的顺序获取数据,而不会丢失重复项或项目
因此:
感谢您提供的帮助。我最初的评估是错误的,我是按照mjwills非常有效的观点来思考的。然而,即使我所指出的解决方案可行,这个特殊病例的根本原因似乎不是突变本身 您遇到的这个问题的根本原因是使用ElementAt() 那么您的代码也应该可以工作,因为在编写foreach行时,我们只隐式地创建一个
IEnumerator
[原始评估-(部分)错误]
orderby ctrl.TabIndex, ctrl.Y, ctrl.X
到
将实现我认为理想的结果,因为TabIndex是您试图计算和更新的东西
从代码中可以看出,它们都以TabIndex=0开始。因此,在这种特殊情况下,仅按X和Y排序意味着更改TabIndex不会影响排序。首先:不要通过
IEnumerable
进行基于索引的访问,而只通过foreach
进行访问。另外,对查询结果调用ToList
。第二:您是否验证了输入集合没有这些重复项?最短的修复方法是在分配给ctrls
后添加一行ctrls=ctrls.ToList()代码>。这里要理解的关键点是,您正在重复执行OrderBy
@HimBromBeere建议使用foreach而不是for可能是更好的解决方案。将向您展示如何使用foreach
获取索引(i)。谢谢。你能解释一下“重复做订单”是什么意思吗?orderby是否在_ctrls.Items…
语句中的var ctrls=from ctrl之后继续运行?i、 当我更改TabIndex?的值时,query不会返回集合,而只是返回一个迭代器。看一看,了解其中的区别。简而言之,在迭代器上多次执行某项操作(在您的例子中,调用ElementAt
)将多次执行底层查询。对于orderby子句,您可能会懒散地计算它,这似乎很奇怪,因为您需要对所有元素进行排序,以确定哪一个元素先到。
关键是要理解LINQ查询更像是一份食谱,而不是一顿饭。您已经告诉它如何订购,但实际上还没有完成订购。想象一下你按名字命令你的孩子。你要第一个,然后改变他们的名字。然后你要求第二个——但是,唉,与此同时,他们被要求反映他们的新名字。所以你可能会再次得到第一个ToList
强制“保持秩序”。在迭代任何类型的IEnumerable/IQueryable时对对象进行变异
从技术上讲,他在迭代时并没有对对象进行变异-但我同意你其余的大部分论点。@mjwillis,谢谢你指出这一点!睡觉后我必须更新/完善我的答案。你完全正确,“支持”列表本身必须在添加或删除项目时发生变化,才能符合我的暗示。我不知道是什么驱使我这么说。
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--;
}
}
}
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()));
}
orderby ctrl.TabIndex, ctrl.Y, ctrl.X
orderby ctrl.Y, ctrl.X