C#,WinForms:ListBox.Items.Add为什么会生成OutOfMemoryException?

10

首先,我找到了解决方案。我更好奇它为什么会生成特定的异常。

在我的场景中,我像这样将POCO添加到ListBox中:

myListBox.Items.Add(myPOCO);

这导致了一个 OutOfMemoryException。问题在于 POCO 的 ToString 返回了 null。我添加了一个 string.IsNullOrEmpty 检查,以在 null 时返回“安全”值,然后异常消失了。
为什么会生成 OutOfMemoryException 而不是其他异常(比如 NullReferenceException)? 编辑:使用 for 循环添加项目。
完整的调用堆栈(已删除公司特定引用)如下。需要注意的一点是,在调用此函数时列表框为空。
System.OutOfMemoryException was unhandled
  Message="List box contains too many items."
  Source="System.Windows.Forms"
  StackTrace:
       at System.Windows.Forms.ListBox.NativeAdd(Object item)
       at System.Windows.Forms.ListBox.ObjectCollection.AddInternal(Object item)
       at System.Windows.Forms.ListBox.ObjectCollection.Add(Object item)
       at <FORM>_Load(Object sender, EventArgs e) in <PATH>\<FORM>.cs:line 52
       at System.Windows.Forms.Form.OnLoad(EventArgs e)
       at System.Windows.Forms.Form.OnCreateControl()
       at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
       at System.Windows.Forms.Control.CreateControl()
       at System.Windows.Forms.Control.WmShowWindow(Message& m)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
       at System.Windows.Forms.ContainerControl.WndProc(Message& m)
       at System.Windows.Forms.Form.WmShowWindow(Message& m)
       at System.Windows.Forms.Form.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.SafeNativeMethods.ShowWindow(HandleRef hWnd, Int32 nCmdShow)
       at System.Windows.Forms.Control.SetVisibleCore(Boolean value)
       at System.Windows.Forms.Form.SetVisibleCore(Boolean value)
       at System.Windows.Forms.Control.set_Visible(Boolean value)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.RunDialog(Form form)
       at System.Windows.Forms.Form.ShowDialog(IWin32Window owner)
       at System.Windows.Forms.Form.ShowDialog()
       at <APP>.Program.Main() in <PATH>\Program.cs:line 25
       at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       at System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args)
       at System.Runtime.Hosting.ManifestRunner.Run(Boolean checkAptModel)
       at System.Runtime.Hosting.ManifestRunner.ExecuteAsAssembly()
       at System.Runtime.Hosting.ApplicationActivator.CreateInstance(ActivationContext activationContext, String[] activationCustomData)
       at System.Runtime.Hosting.ApplicationActivator.CreateInstance(ActivationContext activationContext)
       at System.Activator.CreateInstance(ActivationContext activationContext)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssemblyDebugInZone()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()

1
你是在循环中添加项目吗? - scottm
在什么情况下,ToString 方法会返回 null?当 POCO 非常大时吗? - Dan Tao
你能否提供异常的完整堆栈跟踪信息?我很感兴趣。 - Philip Wallace
ToString返回null是因为我粗心了...POCO基本上只有3个字符串。 - Austin Salonen
@AustinSalonen 你是怎么解决这个问题的? - Smith
3个回答

23

这是因为 System.Windows.Forms.ListBox.NativeAdd 方法的实现方式导致的:

private int NativeAdd(object item)
{
    int num = (int) base.SendMessage(0x180, 0, base.GetItemText(item));
    switch (num)
    {
        case -2:
            throw new OutOfMemoryException();

        case -1:
            throw new OutOfMemoryException(SR.GetString("ListBoxItemOverflow"));
    }
    return num;
}

GetItemText 方法使用了对象的 ToString() 方法,但该方法返回了 null 值,因此发送了一个带有 null 参数的消息,这将返回无效指针并导致进入第二个 case 分支,从而抛出异常。


1
哈哈,我本来想把NativeAdd方法的整个源代码复制到我的答案中,但最终还是放弃了...看来我做出了错误的选择! :-) - Justin Grant
1
@Justin -- 我很矛盾,但我认为这将更好地帮助其他遇到此问题的人。 - Austin Salonen

12

当底层的LB_ADDSTRING Windows API调用失败时,WinForms总是返回一个OutOfMemoryException。 .NET Framework参考源代码中的注释解释了原因:

// On some platforms (e.g. Win98), the ListBox control
// appears to return LB_ERR if there are a large number (>32000)
// of items. It doesn't appear to set error codes appropriately,
// so we'll have to assume that LB_ERR corresponds to item 
// overflow.
// 
throw new OutOfMemoryException(SR.GetString(SR.ListBoxItemOverflow)); 

0
如果出现这种错误,请仔细检查你调用`listbox.Items.Add`或者`listbox.DataSource = xxxxx`的代码。无论添加或者绑定到列表框的对象类型是什么,在`ToString()`方法中一定要返回非空值。默认情况下,如果你没有重载`ToString()`方法,.Net会返回类型名称。
如果你已经实现了你自己的`ToString()`方法(使用override关键字),确保该方法具有返回非null字符串的容错机制。以下示例演示如何添加额外的两行代码以避免许多人会发现完全误导的奇怪错误。
//listbox.Items.Add(new MyItem());
//--or--
//listbox.DataSource= new List<MyItem>(){ new MyItem() };

public class MyItem
{
    public string Name { get; set; }
    public string Label { get; set; }

    public override string ToString()
    {
        //Not adding below 2 lines might throw OutOfMemoryException in listbox.Items.Add or listbox.DataSOurce = somelist
        if (string.IsNullOrEmpty(Label)) //Added this check to avoid 
            return this.GetType().Name; //Return string.Empty or something other than null else you will get OutOfMemoryException error when you add or bind this object to listbox

        return Label;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接