Skip to content

Commit a3ca32b

Browse files
committed
Add convenience / performance related methods to ListViewItemCollection / ControlCollection
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 `[..]`, but it does avoid the need for it in some cases.
1 parent 6bd932c commit a3ca32b

7 files changed

+112
-126
lines changed

src/System.Windows.Forms/src/GlobalSuppressions.cs

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
[assembly: SuppressMessage("Naming", "CA1725:Parameter names should match base declaration", Justification = "Public API", Scope = "member", Target = "~M:System.Windows.Forms.FontDialog.RunDialog(System.IntPtr)~System.Boolean")]
1919
[assembly: SuppressMessage("Naming", "CA1725:Parameter names should match base declaration", Justification = "Public API", Scope = "member", Target = "~M:System.Windows.Forms.ImageListStreamer.GetObjectData(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)")]
2020
[assembly: SuppressMessage("Naming", "CA1725:Parameter names should match base declaration", Justification = "Public API", Scope = "member", Target = "~M:System.Windows.Forms.Control.EndInvoke(System.IAsyncResult)~System.Object")]
21+
[assembly: SuppressMessage("Naming", "CA1725:Parameter names should match base declaration", Justification = "Public API", Scope = "member", Target = "~M:System.Windows.Forms.Control.ControlCollection.IndexOf(System.Windows.Forms.Control)~System.Int32")]
22+
[assembly: SuppressMessage("Naming", "CA1725:Parameter names should match base declaration", Justification = "Public API", Scope = "member", Target = "~M:System.Windows.Forms.ListView.ListViewItemCollection.CopyTo(System.Array,System.Int32)")]
2123
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Public API", Scope = "member", Target = "~F:System.Windows.Forms.FontDialog.EventApply")]
2224
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Public API", Scope = "member", Target = "~F:System.Windows.Forms.FileDialog.EventFileOk")]
2325
[assembly: SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Public API", Scope = "type", Target = "~T:System.Windows.Forms.ImeModeConversion")]
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
@@ -4773,20 +4773,14 @@ public bool Contains([NotNullWhen(true)] Control? ctl)
47734773
/// should not call base.CreateAccessibilityObject.
47744774
/// </summary>
47754775
[EditorBrowsable(EditorBrowsableState.Advanced)]
4776-
protected virtual AccessibleObject CreateAccessibilityInstance()
4777-
{
4778-
return new ControlAccessibleObject(this);
4779-
}
4776+
protected virtual AccessibleObject CreateAccessibilityInstance() => new ControlAccessibleObject(this);
47804777

47814778
/// <summary>
47824779
/// Constructs the new instance of the Controls collection objects. Subclasses
47834780
/// should not call base.CreateControlsInstance.
47844781
/// </summary>
47854782
[EditorBrowsable(EditorBrowsableState.Advanced)]
4786-
protected virtual ControlCollection CreateControlsInstance()
4787-
{
4788-
return new ControlCollection(this);
4789-
}
4783+
protected virtual ControlCollection CreateControlsInstance() => new ControlCollection(this);
47904784

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

0 commit comments

Comments
 (0)