C# Linq orderby子句似乎正在复制和丢失对象

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

我正在写一个Windows窗体程序。我对林克是相当陌生的。我想订购内存中的对象列表。这些对象属于Ctrl类(表示屏幕上的控件)。它们的属性包括:CtrlID、Name、X、Y、TabIndex。对于很多控件,TabIndex可能是零,所以我想按TabIndex,Y,X对它们排序,然后遍历它们,从1开始设置TabIndex,递增,所以它是连续的

我使用的代码如下:

            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子句,使其成为一个直接选择,那么我将按照数据当前的顺序获取数据,而不会丢失重复项或项目

因此:

  • 为什么我丢失了一些东西
  • 为什么我有重复的对象
  • 我如何让orderby做我期望的事情

  • 感谢您提供的帮助。

    我最初的评估是错误的,我是按照mjwills非常有效的观点来思考的。然而,即使我所指出的解决方案可行,这个特殊病例的根本原因似乎不是突变本身

    您遇到的这个问题的根本原因是使用ElementAt()

    那么您的代码也应该可以工作,因为在编写foreach行时,我们只隐式地创建一个
    IEnumerator

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

  • 正如mjwills在他的评论中漂亮地指出的那样,您的LINQ查询更像是一个食谱,而不是一顿饭,因此在您迭代它时,它可能会重新评估。在迭代任何类型的IEnumerable/IQueryable时对对象进行变异通常会导致长期灾难。这就是您获得重复项/缺少项的原因

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

    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