ComboBox.ObjectCollection.CopyTo(Array destination, int index) copies Entry object, not inner item
.NET version
.NET 8, 10 etc.
Did it work in .NET Framework?
Yes
Did it work in any of the earlier releases of .NET Core or .NET 5+?
No response
Issue description
ComboBox.ObjectCollection.CopyTo(Array destination, int index) "leaks" the Entry objects to destination, instead of copying the actual items.
It should be InnerList[i].Item here:
https://github.com/dotnet/winforms/blob/2f677f9560b5258212c721c17f1e8803581608d7/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.ObjectCollection.cs#L290
cf. CopyTo(object[] destination, int arrayIndex):
https://github.com/dotnet/winforms/blob/2f677f9560b5258212c721c17f1e8803581608d7/src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.ObjectCollection.cs#L275
And also for example ListBox.ObjectCollection.CopyTo(Array destination, int index):
https://github.com/dotnet/winforms/blob/2f677f9560b5258212c721c17f1e8803581608d7/src/System.Windows.Forms/System/Windows/Forms/Controls/ListBoxes/ListBox.ObjectCollection.cs#L289
Steps to reproduce
This code works in .NET 4.8 but throws InvalidCastException in .NET core.
class MyItem
{
}
static void Main(string[] args)
{
var cbo = new ComboBox();
cbo.Items.Add(new MyItem());
var item1 = (MyItem)cbo.Items[0];
var arrayList = new ArrayList(cbo.Items);
var item2 = (MyItem)arrayList[0]; // <-- InvalidCastException
}
This behavior is not a bug. If you need to copy the ComboBox items (the displayed content) into an object[] array, use the overload:
comboBox.Items.CopyTo(objectArray, arrayIndex);
This ensures you get the actual items rather than internal entries. If you need to work through the collection interface or your target array type is not object[] (for example, Entry[]), then use:
((ICollection)comboBox.Items).CopyTo(destinationArray, index);
This follows the standard ICollection.CopyTo contract, which copies the underlying collection elements as they are represented internally. The two overloads serve different purposes: one for copying the user-facing items, and the other for low-level collection semantics. Example code Ⅰ:
ComboBox comboBox = new();
comboBox.Items.AddRange(["Item1", "Item2", "Item3"]);
object[] destination = new object[comboBox.Items.Count];
((ICollection)comboBox.Items).CopyTo(destination, 0);
Example code Ⅱ:
var cbo = new ComboBox();
cbo.Items.Add(new MyItem());
var item1 = (MyItem)cbo.Items[0];
var arrayList = new ArrayList(cbo.Items);
// Repair: Check the type before casting to avoid invalid casting exceptions.
var item2 = arrayList[0] is MyItem mi ? mi : null;
Of course it's a bug!!
How do you explain the difference between ComboBox and ListBox? And the regression from .NET Framework?
Also what would be the point of returning Entry objects which is an internal class and thus not usable by the client?
And to be clear: I would never use ArrayList in new code. This was legacy code that stopped working when migrating from .NET Framework.
var lb = new ListBox();
lb.Items.Add(new MyItem());
var arrayList = new ArrayList(lb.Items);
var item = (MyItem)arrayList[0]; // <-- OK
var cbo = new ComboBox();
cbo.Items.Add(new MyItem());
arrayList = new ArrayList(cbo.Items);
item = (MyItem)arrayList[0]; // <-- InvalidCastException
@LeafShi1, @SimonZhao888: Can you investigate in what context this has regressed? I have a suspicion, but I would need to see an actual PR to confirm.
@LeafShi1, @SimonZhao888: Can you investigate in what context this has regressed? I have a suspicion, but I would need to see an actual PR to confirm.
https://github.com/dotnet/winforms/pull/4193