Why is this array list removal code failing?

社会主义新天地 提交于 2019-12-25 04:07:44

问题


I'm trying to remove specific values from a listbox by passing the corresponding text, looking for it in the listbox, and removing that item. With this code:

private void UpdateGUIAfterTableSend(String listboxVal)
{
    ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend");
    try
    {
        listBoxWork.DataSource = null; // <= It seems necessary to set this to null to circumvent "Value does not fall within the expected range"
        for (int i = listBoxWork.Items.Count - 1; i >= 0; --i)
        {
            if (listBoxWork.Items[i].ToString().Contains(listboxVal))
            {
                listBoxWork.Items.RemoveAt(i);
            }
        }
    }
    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));
    }
}

...though, it fails; the problem is logged as:

Date: 2/10/2015 1:48:07 PM
Message: Reached frmMain.UpdateGUIAfterTableSend

Date: 2/10/2015 1:48:07 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()

Note that it's the "application-wide exception handler" that logs the exception, not the catch block in UpdateGUIAfterTableSend()

The list box is populated by setting its datasource like so:

listBoxWork.DataSource = workTables;

workTables is a list of string populated by a query:

List<String> workTables = hhsdbutils.GetWorkTableNames();

I'm confused both as to what is causing the problem (and its solution) and why the catch block in UpdateGUIAfterTableSend() is not logging the exception that is being thrown.

UPDATE

After Steve's comment, I uncommented the setting of the datasource to null. the exception then is:

Date: 2/10/2015 2:19:58 PM
Message: From frmMain.UpdateGUIAfterTableSend: Value does not fall within the expected range.; Inner Ex: ; Stack Trace:    at System.Windows.Forms.ListBox._VerifyNoDataSource()
   at System.Windows.Forms.ListBox.ObjectCollection.RemoveAt(Int32 index)
   at HHS.frmMain.UpdateGUIAfterTableSend(String listboxVal)

UPDATE 2

For Adi Lester:

private void SendDeliveries()
{
    ExceptionLoggingService .Instance.WriteLog("Reached frmMain.SendDeliveries");
    Cursor curse = Cursor.Current;
    Cursor.Current = Cursors.WaitCursor;
    try
    {
        bool firstRecord = false;
        bool lastRecord = false;
        foreach (String tblname in listBoxWork.Items)
        {
            // Ignore INV tables
            if (tblname.IndexOf("INV") == 0) continue; 
            String tblSiteNum = hhsdbutils.GetSiteNumForTableName(tblname);
            String fileName = HHSUtils.GetGeneratedDSDFileName(tblSiteNum);
            String xmlData = hhsdbutils.GetDSDDataAsXMLFromTable(tblname, fileName);
            String uri = String.Format("{0}delivery/sendXML/duckbill/platypus/{1}", HHSConsts.BASE_REST_URL, fileName);
            fileXferImp = HHSConsts.GetFileTransferMethodology();
            fileXferImp.SendDataContentsAsXML(uri, xmlData, tblname, siteNum, firstRecord, lastRecord);
            String tableRefVal = HHSUtils.GetTableRefValForTableName(tblname, "DSD");
            hhsdbutils.DeleteTableReference(tableRefVal, "DSD");
            hhsdbutils.DropTable(tblname, tblSiteNum); // <- Will actually do this only after creating replacement tables in code
            UpdateGUIAfterTableSend(tblname);
        }
    }
    finally
    {
        Cursor.Current = curse;
    }
}

回答1:


When setting the DataSource of the ListBox use a BindingSource instead of directly using the List(Of String) This is required otherwise any change in the DataSource will not be seen by the binding engine.

Supposing that you want to set your listbox items with something like this and want to remove every item that contains the string "Test":

listBoxWork = new ListBox();
List<string> elements = new List<string>()
{
    "Test1", "Test2", "Test3", "Example1", "Example2"
};

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

Now in your code that tries to remove the items use

private void UpdateGUIAfterTableSend(String listboxVal)
{
    ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend");
    try
    {
        BindingSource bs = listBoxWork.DataSource as BindingSource;
        for (int i = bs.Count - 1; i >= 0; --i)
        {
            if (bs[i].ToString().Contains("Test"))
            {
                bs.RemoveAt(i);
            }
        }
    }
    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));
    }
}



回答2:


Both of your approaches are wrong. I mean both setting the DataSource to null, and Removing the Items from ListBox directly.

When ListBox is data bound(i.e DataSource set) you can't modify the Items collection directly. You must need to remove the item in the underlying data source. The data source should notify the data binding engine about the removal of item, so that the ListBox Items will be in sync.

For that, your DataSource should support change notification. If WPF, use ObservableCollection. If winforms, use BindingList<T>.

So your code should look like:

for (int i = yourDataSource.Count - 1; i >= 0; --i)
{
    if (yourDataSource[i].ToString().Contains(listboxVal))
    {
        yourDataSource.RemoveAt(i);
    }
}



回答3:


While you have not provided the actual code that throws the exception it is fairly obvious that you are removing an item from a list that is being enumerated (probably via foreach). The standard collection invalidate any enumerator if the collection is changed. Removing in a for loop is not the problem and this is why you don't catch an exception there. It seems like this method is called in another method inside a foreach loop.



来源:https://stackoverflow.com/questions/28439941/why-is-this-array-list-removal-code-failing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!