为什么删除列表框项目会在循环中第二次崩溃?

Why does removing a listbox Item crash the second time through the loop?

这是一个持续存在的问题;在下面的代码中,旧代码(也失败了)被注释掉了。不过,新代码的行为方式相同:

private void UpdateGUIAfterTableSend(String listboxVal)
{
    ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend");
    try
    {
        //listBoxWork.DataSource = null; // <= This (at one time, anyway) seemed necessary to circumvent "Value does not fall within the expected range"
        //// Failing ignominiously; question at 
        //for (int i = listBoxWork.Items.Count - 1; i >= 0; --i) // try a foreach instead?
        //{
        //    if (listBoxWork.Items[i].ToString().Contains(listboxVal))
        //    {
        //        listBoxWork.Items.RemoveAt(i);
        //    }
        //}
        BindingSource bs = listBoxWork.DataSource as BindingSource;
        ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend#2");
        for (int i = bs.Count - 1; i >= 0; --i)
        {
            if (bs[i].ToString().Contains(listboxVal))
            {
                bs.RemoveAt(i);
                ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend#3");
            }
        }
    }
    catch (Exception ex)
    {
        String msgInnerExAndStackTrace = String.Format("{0}; Inner Ex: {1}; Stack Trace: {2}", ex.Message, ex.InnerException, ex.StackTrace);
        ExceptionLoggingService.Instance.WriteLog(String.Format("From frmMain.UpdateGUIAfterTableSend: {0}", msgInnerExAndStackTrace));
    }
}

好像是第二次通过for循环,app突然崩溃了。我这样说是因为日志文件显示 "Reached frmMain.UpdateGUIAfterTableSend#3" 而不是来自 catch 块的日志消息。所以它一定是在四循环的第二次迭代中崩溃了(在再次记录 "Reached frmMain.UpdateGUIAfterTableSend#3" 之前)。所以一旦崩溃就晚安了,Irene:它崩溃得如此之快,以至于它甚至没有记录 catch 块的日志消息。还是有别的解释?

但更重要的是:为什么第二次检查 "Contains" 或第二次尝试删除(在我的测试用例中不应该发生,列表框只有一个项目)引爆了代码-消失了-野核装置?

这是崩溃后的最后一个日志文件:

Date: 2/11/2015 12:45:34 PM
Message: Reached frmMain.UpdateGUIAfterTableSend

Date: 2/11/2015 12:45:34 PM
Message: Reached frmMain.UpdateGUIAfterTableSend#2

Date: 2/11/2015 12:45:34 PM
Message: Reached frmMain.UpdateGUIAfterTableSend#3

Date: 2/11/2015 12:45:34 PM
Message: From application-wide exception handler: System.InvalidOperationException: InvalidOperationException
   at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()
   at HHS.frmMain.SendDeliveries()
   at HHS.frmMain.menuItemSEND_Deliveries_Click(Object sender, EventArgs e)
   at System.Windows.Forms.MenuItem.OnClick(EventArgs e)
   at System.Windows.Forms.Menu.ProcessMnuProc(Control ctlThis, WM wm, Int32 wParam, Int32 lParam)
   at System.Windows.Forms.Form.WnProc(WM wm, Int32 wParam, Int32 lParam)
   at System.Windows.Forms.Control._InternalWnProc(WM wm, Int32 wParam, Int32 lParam)
   at Microsoft.AGL.Forms.EVL.EnterMainLoop(IntPtr hwnMain)
   at System.Windows.Forms.Application.Run(Form fm)
   at HHS.Program.Main()

更新

我在 "RemoveAt" 行之前添加了另一个日志消息,得到了我期望的结果:

Date: 2/11/2015 1:36:53 PM
Message: About to remove listbox value DSD_3_20150209151047000 at index 0

...但之后立即崩溃,所以 Rake36 一定是正确的。

更新 2

我有点相信这会奏效:

private void UpdateGUIAfterTableSend(String listboxVal)
{
    ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend");
    try
    {
        ExceptionLoggingService.Instance.WriteLog(String.Format("About to remove listbox value {0}", listboxVal));
        listBoxWork.Items.RemoveAt(listBoxWork.Items.IndexOf(listboxVal));
    }
    catch (Exception ex)
    {
        String msgInnerExAndStackTrace = String.Format("{0}; Inner Ex: {1}; Stack Trace: {2}", ex.Message, ex.InnerException, ex.StackTrace);
        ExceptionLoggingService.Instance.WriteLog(String.Format("From frmMain.UpdateGUIAfterTableSend: {0}", msgInnerExAndStackTrace));
    }
}

...但是当我听到这个故事 "bong!" 然后在日志文件中查找时,我的希望破灭了:

Message: About to remove listbox value DSD_3_20150209151047000

Date: 2/11/2015 1:58:18 PM
Message: From frmMain.UpdateGUIAfterTableSend: Specified argument was out of the range of valid values.; Inner Ex: ; Stack Trace:    at System.Collections.ArrayList.RemoveAt(Int32 index)
   at System.Windows.Forms.ListBox.ObjectCollection.RemoveAt(Int32 index)
   at HHS.frmMain.UpdateGUIAfterTableSend(String listboxVal)
   at HHS.frmMain.SendDeliveries()
   at HHS.frmMain.menuItemSEND_Deliveries_Click(Object sender, EventArgs e)
   at System.Windows.Forms.MenuItem.OnClick(EventArgs e)
   at System.Windows.Forms.Menu.ProcessMnuProc(Control ctlThis, WM wm, Int32 wParam, Int32 lParam)
   at System.Windows.Forms.Form.WnProc(WM wm, Int32 wParam, Int32 lParam)
   at System.Windows.Forms.Control._InternalWnProc(WM wm, Int32 wParam, Int32 lParam)
   at Microsoft.AGL.Forms.EVL.EnterMainLoop(IntPtr hwnMain)
   at System.Windows.Forms.Application.Run(Form fm)
   at HHS.Program.Main()


Date: 2/11/2015 1:58:19 PM
Message: From application-wide exception handler: System.InvalidOperationException: InvalidOperationException
   at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()
   at HHS.frmMain.SendDeliveries()
   at HHS.frmMain.menuItemSEND_Deliveries_Click(Object sender, EventArgs e)
   at System.Windows.Forms.MenuItem.OnClick(EventArgs e)
   at System.Windows.Forms.Menu.ProcessMnuProc(Control ctlThis, WM wm, Int32 wParam, Int32 lParam)
   at System.Windows.Forms.Form.WnProc(WM wm, Int32 wParam, Int32 lParam)
   at System.Windows.Forms.Control._InternalWnProc(WM wm, Int32 wParam, Int32 lParam)
   at Microsoft.AGL.Forms.EVL.EnterMainLoop(IntPtr hwnMain)
   at System.Windows.Forms.Application.Run(Form fm)
   at HHS.Program.Main()

我什至在 "RemoveAt" 行之前添加了这个:

listBoxWork.DataSource = null;

...但没有任何区别。

更新 3

这个(灵感来自 Ravi M Patel)也崩溃了:

private void UpdateGUIAfterTableSend(String listboxVal)
{
    ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend");
    try
    {
        BindingSource bs = new BindingSource();
        bs.DataSource = listBoxWork.DataSource;
        for (int i = bs.Count - 1; i >= 0; i--)
        {
            if (bs[i].ToString().Contains(listboxVal))
            {
                ExceptionLoggingService.Instance.WriteLog(String.Format("About to remove listbox value {0} at index {1}", listboxVal, i));
                bs.RemoveAt(i);
                ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend#3");
            }
        }
    }
    catch (Exception ex)
    {
        String msgInnerExAndStackTrace = String.Format("{0}; Inner Ex: {1}; Stack Trace: {2}", ex.Message, ex.InnerException, ex.StackTrace);
        ExceptionLoggingService.Instance.WriteLog(String.Format("From frmMain.UpdateGUIAfterTableSend: {0}", msgInnerExAndStackTrace));
    }
}

...日志文件的(相关)内容为:

Date: 2/11/2015 2:09:59 PM
Message: About to remove listbox value DSD_3_20150209151047000 at index 0

Date: 2/11/2015 2:09:59 PM
Message: Reached frmMain.UpdateGUIAfterTableSend#3

Date: 2/11/2015 2:09:59 PM
Message: From application-wide exception handler: System.InvalidOperationException: InvalidOperationException
   at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()
   at HHS.frmMain.SendDeliveries()

更新 4

有关真正的修复,请参阅

您不能在迭代时销毁列表中的元素。您需要先收集要删除的项目,然后遍历这些项目,在此过程中从原始列表中删除。

有用的模式:

Items.RemoveAt(Items.IndexOf(itemToDelete))

更新 2

将 try{} 块中的代码替换为以下代码:

BindingSource bs = listBoxWork.DataSource as BindingSource;
List<string> values = bs.DataSource as List<string>;
values.RemoveAll(v => v.Contains(listboxVal));
bs.ResetBindings(false);

更新

@TaW 说的对。我正在疯狂猜测这个问题。当您从数据源中删除一个元素时,它也会更新 ListBox。由于您要删除 selected 值,因此 UI 正在尝试 select 下一个值和 bang,还有none.

因此,要使这项工作成功,您应该这样做。假设您已将列表框绑定到名为 values.

的数组列表

现在,在您的方法中,从 values 而不是 BindingSource 中删除项目。在您的情况下,如果确定列表中只有一个值。您所要做的就是 values.Remove("valuetoremove"); 然后像这样重置绑定。

ArrayList values = bs.DataSource as ArrayList;
values.Remove("valuetoremove");
bs.ResetBindings(false);

你可以无视我下面的回答。我认为您要进行类型转换的数据源已经是 BindingSource 类型,因此您对原始声明很满意。 BindingSource bs = listBoxWork.DataSource as BindingSource; 我不知道,因为我们在这里看不到完整的代码。


如果您想要绑定源的正确行为。你最好这样用。

BindingSource bs = new BindingSource();
bs.DataSource = listBoxWork.DataSource;

用您的原始代码试试。

异常来自与您显示的逻辑完全不同的地方。触发的是应用程序范围的异常处理程序,而不是将创建跟踪的异常处理程序 "From frmMain.UpdateGUIAfterTableSend"。异常很可能是因为绑定有副作用。查看绑定源和附加到它的事件。 还要在删除后添加跟踪,以便您可以看到删除确实发生了,或者使用 debugger.Your 更新 3 显示删除确实发生了。

在 SendDeliveries 中添加更多跟踪。你的问题在那里

如果你能做到:

List<string> list = (List<string>) listBoxWork.DataSource;

你应该可以做到这一点:

    for (int i = 0; i < list.Count; i++)
    {
        if (list[i].Contains(listboxVal))
        {
            list.RemoveAt(i);
        }
    }

    listBox1.DataSource = null;
    listBox1.Items.Clear();
    listBox1.DataSource = list;

但我怀疑你的 DataSource 实际上是 List<string> 因为这根本行不通。 (只会显示字符串的长度!)

因此您应该修改代码以适合您的实际类型 DataSource!

如果它是这样的:

List<AString> list = new List<AString>();

一个class喜欢:

class AString
{
    public string s { get; set; }
    public AString(string s_) { s = s_; }
    public override string ToString()  {   return s;    }
}

那么这会起作用:

    for (int i = list.Count - 1; i > 0;  i--)
    {
        if (list[i].s.Contains(listboxVal))
        {
            list.RemoveAt(i); 
        }
    }       
    listBox1.DataSource = null;
    listBox1.Items.Clear();
    listBox1.DataSource = list;

注意ListBox doesn't automatically updateDataSource被修改。

您可以使用 BindingList:

而不是进行这种令人不满意的往返
    BindingList<AString> data = new BindingList<AString>();
    foreach (AStrings in list) data.Add(s);
    listBox1.DataSource = data;

    for (int i = data.Count - 1; i > 0; i--)
    {
        if (data[i].s.Contains("8"))
        {
            data.RemoveAt(i); // (list[i]);
        }
    }

现在删除会立即反映出来..