Skip to content

Commit 2bad759

Browse files
committed
ListViewItemCollection doesn't have an AddRange that takes an IEnumerable<ListViewItem>, which necessitates unnecessary array allocations when adding from a collection.
It also doesn't implement a generic `IList<T>` which makes typed usage and LINQ usage difficult. This adds that. Also add `IEnumerable<Control>` to `ControlCollection` to address the LINQ scenario. Control has `IList`, but indexed setting throws. I don't want to add `IList<Control>` until we've had time to evaluate the implications of implicitly doing the public steps with the collection that replicate what those setters should be doing. Also tweak `ArrangedElementCollection` so the static empty collection can't be written to. This doesn't fix the ambiguity of `AddRange([..])` with `ListViewItemCollection`, but it does avoid the need for it in some cases.
1 parent 1e92b29 commit 2bad759

File tree

7 files changed

+93
-66
lines changed

7 files changed

+93
-66
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
System.Windows.Forms.ListView.ListViewItemCollection.AddRange(System.Collections.Generic.IEnumerable<System.Windows.Forms.ListViewItem!>! collection) -> void
2+
System.Windows.Forms.ListView.ListViewItemCollection.CopyTo(System.Windows.Forms.ListViewItem![]! array, int arrayIndex) -> void

src/System.Windows.Forms/src/System/Windows/Forms/Control.ControlCollection.cs

+31-24
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ namespace System.Windows.Forms;
1010
public partial class Control
1111
{
1212
/// <summary>
13-
/// Collection of controls...
13+
/// Collection of controls.
1414
/// </summary>
1515
[ListBindable(false)]
16-
public partial class ControlCollection : ArrangedElementCollection, IList, ICloneable
16+
public partial class ControlCollection : ArrangedElementCollection, IList, IEnumerable<Control>, ICloneable
1717
{
1818
/// A caching mechanism for key accessor
1919
/// We use an index here rather than control so that we don't have lifetime
@@ -29,10 +29,7 @@ public ControlCollection(Control owner)
2929
/// <summary>
3030
/// Returns true if the collection contains an item with the specified key, false otherwise.
3131
/// </summary>
32-
public virtual bool ContainsKey(string? key)
33-
{
34-
return IsValidIndex(IndexOfKey(key));
35-
}
32+
public virtual bool ContainsKey(string? key) => IsValidIndex(IndexOfKey(key));
3633

3734
/// <summary>
3835
/// Adds a child control to this control. The control becomes the last control in
@@ -156,11 +153,23 @@ object ICloneable.Clone()
156153
{
157154
// Use CreateControlInstance so we get the same type of ControlCollection, but whack the
158155
// owner so adding controls to this new collection does not affect the control we cloned from.
159-
ControlCollection ccOther = Owner.CreateControlsInstance();
156+
ControlCollection clone = Owner.CreateControlsInstance();
160157

161158
// We add using InnerList to prevent unnecessary parent cycle checks, etc.
162-
ccOther.InnerList.AddRange(InnerList);
163-
return ccOther;
159+
if (clone.InnerList is List<IArrangedElement> list)
160+
{
161+
list.AddRange(InnerList);
162+
}
163+
else
164+
{
165+
Debug.Fail("Unexpected InnerList type");
166+
foreach (Control c in this)
167+
{
168+
clone.Add(c);
169+
}
170+
}
171+
172+
return clone;
164173
}
165174

166175
public bool Contains(Control? control) => ((IList)InnerList).Contains(control);
@@ -224,10 +233,7 @@ private static void FindInternal(string key, bool searchAllChildren, ControlColl
224233
}
225234
}
226235

227-
public override IEnumerator GetEnumerator()
228-
{
229-
return new ControlCollectionEnumerator(this);
230-
}
236+
public override IEnumerator GetEnumerator() => new ControlCollectionEnumerator(this);
231237

232238
public int IndexOf(Control? control) => ((IList)InnerList).IndexOf(control);
233239

@@ -269,10 +275,7 @@ public virtual int IndexOfKey(string? key)
269275
/// <summary>
270276
/// Determines if the index is valid for the collection.
271277
/// </summary>
272-
private bool IsValidIndex(int index)
273-
{
274-
return ((index >= 0) && (index < Count));
275-
}
278+
private bool IsValidIndex(int index) => (index >= 0) && (index < Count);
276279

277280
/// <summary>
278281
/// Who owns this control collection.
@@ -285,10 +288,9 @@ private bool IsValidIndex(int index)
285288
/// </summary>
286289
public virtual void Remove(Control? value)
287290
{
288-
// Sanity check parameter
289291
if (value is null)
290292
{
291-
return; // Don't do anything
293+
return;
292294
}
293295

294296
if (value.ParentInternal == Owner)
@@ -317,10 +319,7 @@ void IList.Remove(object? control)
317319
}
318320
}
319321

320-
public void RemoveAt(int index)
321-
{
322-
Remove(this[index]);
323-
}
322+
public void RemoveAt(int index) => Remove(this[index]);
324323

325324
/// <summary>
326325
/// Removes the child control with the specified key.
@@ -394,7 +393,7 @@ public virtual void Clear()
394393
/// Retrieves the index of the specified child control in this array. An ArgumentException
395394
/// is thrown if child is not parented to this Control.
396395
/// </summary>
397-
public int GetChildIndex(Control child) => GetChildIndex(child, true);
396+
public int GetChildIndex(Control child) => GetChildIndex(child, throwException: true);
398397

399398
/// <summary>
400399
/// Retrieves the index of the specified child control in this array. An ArgumentException
@@ -444,5 +443,13 @@ internal virtual void SetChildIndexInternal(Control child, int newIndex)
444443
/// </summary>
445444
public virtual void SetChildIndex(Control child, int newIndex) =>
446445
SetChildIndexInternal(child, newIndex);
446+
447+
IEnumerator<Control> IEnumerable<Control>.GetEnumerator()
448+
{
449+
foreach (Control control in InnerList)
450+
{
451+
yield return control;
452+
}
453+
}
447454
}
448455
}

src/System.Windows.Forms/src/System/Windows/Forms/Control.cs

+2-8
Original file line numberDiff line numberDiff line change
@@ -4695,20 +4695,14 @@ public bool Contains([NotNullWhen(true)] Control? ctl)
46954695
/// should not call base.CreateAccessibilityObject.
46964696
/// </summary>
46974697
[EditorBrowsable(EditorBrowsableState.Advanced)]
4698-
protected virtual AccessibleObject CreateAccessibilityInstance()
4699-
{
4700-
return new ControlAccessibleObject(this);
4701-
}
4698+
protected virtual AccessibleObject CreateAccessibilityInstance() => new ControlAccessibleObject(this);
47024699

47034700
/// <summary>
47044701
/// Constructs the new instance of the Controls collection objects. Subclasses
47054702
/// should not call base.CreateControlsInstance.
47064703
/// </summary>
47074704
[EditorBrowsable(EditorBrowsableState.Advanced)]
4708-
protected virtual ControlCollection CreateControlsInstance()
4709-
{
4710-
return new ControlCollection(this);
4711-
}
4705+
protected virtual ControlCollection CreateControlsInstance() => new ControlCollection(this);
47124706

47134707
/// <summary>
47144708
/// Creates a Graphics for this control. The control's brush, font, foreground

src/System.Windows.Forms/src/System/Windows/Forms/Controls/ListView/ListView.ListViewItemCollection.cs

+43-23
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public partial class ListView
1212
/// Represents the collection of items in a ListView or ListViewGroup
1313
/// </summary>
1414
[ListBindable(false)]
15-
public partial class ListViewItemCollection : IList
15+
public partial class ListViewItemCollection : IList, IList<ListViewItem>
1616
{
1717
/// A caching mechanism for key accessor
1818
/// We use an index here rather than control so that we don't have lifetime
@@ -75,10 +75,7 @@ public virtual ListViewItem this[int index]
7575

7676
object? IList.this[int index]
7777
{
78-
get
79-
{
80-
return this[index];
81-
}
78+
get => this[index];
8279
set
8380
{
8481
this[index] = value is ListViewItem item
@@ -116,10 +113,7 @@ public virtual ListViewItem? this[string key]
116113
/// the correct sorted position, or, if no sorting is set, at the end
117114
/// of the list.
118115
/// </summary>
119-
public virtual ListViewItem Add(string? text)
120-
{
121-
return Add(text, -1);
122-
}
116+
public virtual ListViewItem Add(string? text) => Add(text, -1);
123117

124118
int IList.Add(object? item)
125119
{
@@ -159,8 +153,6 @@ public virtual ListViewItem Add(ListViewItem value)
159153
return value;
160154
}
161155

162-
// <-- NEW ADD OVERLOADS IN WHIDBEY
163-
164156
/// <summary>
165157
/// Add an item to the ListView. The item will be inserted either in
166158
/// the correct sorted position, or, if no sorting is set, at the end
@@ -203,8 +195,6 @@ public virtual ListViewItem Add(string? key, string? text, int imageIndex)
203195
return item;
204196
}
205197

206-
// END - NEW ADD OVERLOADS IN WHIDBEY -->
207-
208198
public void AddRange(params ListViewItem[] items)
209199
{
210200
ArgumentNullException.ThrowIfNull(items);
@@ -224,10 +214,7 @@ public void AddRange(ListViewItemCollection items)
224214
/// <summary>
225215
/// Removes all items from the list view.
226216
/// </summary>
227-
public virtual void Clear()
228-
{
229-
InnerList.Clear();
230-
}
217+
public virtual void Clear() => InnerList.Clear();
231218

232219
public bool Contains(ListViewItem item)
233220
{
@@ -335,7 +322,7 @@ public virtual int IndexOfKey(string? key)
335322
// step 1 - check the last cached item
336323
if (IsValidIndex(_lastAccessedIndex))
337324
{
338-
if (WindowsFormsUtils.SafeCompareStrings(this[_lastAccessedIndex].Name, key, /* ignoreCase = */ true))
325+
if (WindowsFormsUtils.SafeCompareStrings(this[_lastAccessedIndex].Name, key, ignoreCase: true))
339326
{
340327
return _lastAccessedIndex;
341328
}
@@ -344,7 +331,7 @@ public virtual int IndexOfKey(string? key)
344331
// step 2 - search for the item
345332
for (int i = 0; i < Count; i++)
346333
{
347-
if (WindowsFormsUtils.SafeCompareStrings(this[i].Name, key, /* ignoreCase = */ true))
334+
if (WindowsFormsUtils.SafeCompareStrings(this[i].Name, key, ignoreCase: true))
348335
{
349336
_lastAccessedIndex = i;
350337
return i;
@@ -395,8 +382,6 @@ void IList.Insert(int index, object? item)
395382
}
396383
}
397384

398-
// <-- NEW INSERT OVERLOADS IN WHIDBEY
399-
400385
public ListViewItem Insert(int index, string? text, string? imageKey)
401386
=> Insert(index, new ListViewItem(text, imageKey));
402387

@@ -412,8 +397,6 @@ public virtual ListViewItem Insert(int index, string? key, string? text, int ima
412397
Name = key
413398
});
414399

415-
// END - NEW INSERT OVERLOADS IN WHIDBEY -->
416-
417400
/// <summary>
418401
/// Removes an item from the ListView
419402
/// </summary>
@@ -452,5 +435,42 @@ void IList.Remove(object? item)
452435
Remove(listViewItem);
453436
}
454437
}
438+
439+
void IList<ListViewItem>.Insert(int index, ListViewItem item) => Insert(index, item);
440+
441+
void ICollection<ListViewItem>.Add(ListViewItem item) => Add(item);
442+
443+
public void CopyTo(ListViewItem[] array, int arrayIndex) => CopyTo((Array)array, arrayIndex);
444+
445+
bool ICollection<ListViewItem>.Remove(ListViewItem item)
446+
{
447+
if (Contains(item))
448+
{
449+
Remove(item);
450+
return true;
451+
}
452+
453+
return false;
454+
}
455+
456+
IEnumerator<ListViewItem> IEnumerable<ListViewItem>.GetEnumerator()
457+
{
458+
var enumerator = GetEnumerator();
459+
while (enumerator.MoveNext())
460+
{
461+
if (enumerator.Current is ListViewItem item)
462+
{
463+
yield return item;
464+
}
465+
}
466+
}
467+
468+
public void AddRange(IEnumerable<ListViewItem> collection)
469+
{
470+
foreach (ListViewItem item in collection)
471+
{
472+
Add(item);
473+
}
474+
}
455475
}
456476
}

src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripPanel.ToolStripPanelControlCollection.cs

+5-6
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,13 @@ internal override void AddInternal(Control? value)
3434

3535
internal void Sort()
3636
{
37-
if (_owner.Orientation == Orientation.Horizontal)
37+
if (InnerList is not List<IArrangedElement> list)
3838
{
39-
InnerList.Sort(new YXComparer());
40-
}
41-
else
42-
{
43-
InnerList.Sort(new XYComparer());
39+
Debug.Fail("InnerList is not a List<IArrangedElement>?");
40+
return;
4441
}
42+
43+
list.Sort(_owner.Orientation == Orientation.Horizontal ? new YXComparer() : new XYComparer());
4544
}
4645
}
4746
}

src/System.Windows.Forms/src/System/Windows/Forms/Layout/ArrangedElementCollection.cs

+9-4
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@ namespace System.Windows.Forms.Layout;
77

88
public class ArrangedElementCollection : IList
99
{
10-
internal static ArrangedElementCollection Empty { get; } = new(0);
10+
internal static ArrangedElementCollection Empty { get; } = new(new List<IArrangedElement>().AsReadOnly());
1111

1212
internal ArrangedElementCollection() : this(4)
1313
{
1414
}
1515

16-
internal ArrangedElementCollection(List<IArrangedElement> innerList) => InnerList = innerList;
16+
internal ArrangedElementCollection(IList<IArrangedElement> innerList) => InnerList = innerList;
1717

1818
private ArrangedElementCollection(int size) => InnerList = new List<IArrangedElement>(size);
1919

20-
private protected List<IArrangedElement> InnerList { get; }
20+
private protected IList<IArrangedElement> InnerList { get; }
2121

2222
internal virtual IArrangedElement this[int index] => InnerList[index];
2323

@@ -93,7 +93,12 @@ private protected void MoveElement(IArrangedElement element, int fromIndex, int
9393
InnerList[toIndex] = element;
9494
}
9595

96-
private static void Copy(ArrangedElementCollection sourceList, int sourceIndex, ArrangedElementCollection destinationList, int destinationIndex, int length)
96+
private static void Copy(
97+
ArrangedElementCollection sourceList,
98+
int sourceIndex,
99+
ArrangedElementCollection destinationList,
100+
int destinationIndex,
101+
int length)
97102
{
98103
if (sourceIndex < destinationIndex)
99104
{

src/System.Windows.Forms/tests/UnitTests/SerializableTypesTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ static void ValidateResult(string blob)
146146
Assert.Equal(HorizontalAlignment.Center, result.HeaderAlignment);
147147
Assert.Equal("Tag", result.Tag);
148148
Assert.Equal("GroupName", result.Name);
149-
var item = Assert.Single(result.Items) as ListViewItem;
149+
var item = Assert.Single(result.Items);
150150
Assert.NotNull(item);
151151
Assert.Equal("Item", item.Text);
152152
}

0 commit comments

Comments
 (0)