Is duplicate string index in ControlCollection possible? And how does it work?

143 Views Asked by At

I'm starting a small project in WinForms C# and have something like this:

UserControl uc1 = new UserControl();
UserControl uc2 = new UserControl();
mainPanel.Controls.Add(uc1);
mainPanel.Controls.Add(uc2);
Console.WriteLine(mainPanel.Controls["UserControl"].Width);

Knowing that mainPanel.Controls is a collection, how come that UserControl - class name of uc1 and uc2 - can act like an index? And if so, these 2 elements in the collection have the same index, namely UserControl, is this even possible? Or this is something particular of .NET Framework?

2

There are 2 best solutions below

0
On BEST ANSWER

It's possible in this collection

If you take a look at the source code for the ControlCollection you'll see the indexer that takes a string:

        public virtual Control this[string key] {
            get {
                // We do not support null and empty string as valid keys.
                if (String.IsNullOrEmpty(key)) {
                    return null;
                }

                // Search for the key in our collection
                int index = IndexOfKey(key);
                if (IsValidIndex(index)) {
                    return this[index];
                }
                else {
                    return null;
                }

            }
        }

Controls are kept in an (Array)list and IndexOfKey is used to find the first index of the given name:

        public virtual int IndexOfKey(String key) {
            // Step 0 - Arg validation
            if (String.IsNullOrEmpty(key)) {
                return -1; // we dont support empty or null keys.
            }

            // step 1 - check the last cached item
            if (IsValidIndex(lastAccessedIndex))
            {
                if (WindowsFormsUtils.SafeCompareStrings(this[lastAccessedIndex].Name, key, /* ignoreCase = */ true)) {
                    return lastAccessedIndex;
                }
            }

            // step 2 - search for the item
            for (int i = 0; i < this.Count; i ++) {
                if (WindowsFormsUtils.SafeCompareStrings(this[i].Name, key, /* ignoreCase = */ true)) {
                    lastAccessedIndex = i;
                    return i;
                }
            }

            // step 3 - we didn't find it.  Invalidate the last accessed index and return -1.
            lastAccessedIndex = -1;
            return -1;
        }

There isn't any uniqueness constraint in this code.. it's just "find the first added one with that string"


Side note; it's interesting to look at such old code (ArrayList) and consider how it might be written now that we have more powerful collections, LINQ, dictionaries, named parameters.. clearly an optimization there that MS expect people to repeatedly use string lookup, in loops etc, so they keep the last used string and compare this string to see.. Whicj in turn I guess means that for best resource use if you know you're cmgoing to repeatedly access a control by the same string (in a loop?) access it by number index instead..

2
On

If we take a look at the following code, what happens

var uc1 = new UserControl();
var uc2 = new UserControl();

//We have to set the name, because one is not assigned automatically via code, only when added via Designer
uc1.Name = "notUnique";
uc2.Name = "notUnique";

//We add unique text values so that we can distinguish them uniquely for the purpose of this test
uc1.Text = "uc1";
uc2.Text = "uc2";
            
panel1.Controls.Add(uc1);
panel1.Controls.Add(uc2);

var control = panel1.Controls["notUnique"];

The result is that control is uc1. But if we swap the addition of the controls, so that we add uc2 into the collection first

panel1.Controls.Add(uc2);
panel1.Controls.Add(uc1);


var control = panel1.Controls["notUnique"];

Then in this case control = uc2

So then it is clear that the index returns the first item it finds which matches (or null if none match).

Interestingly, this means if you also remove the control by using the key that it will remove the first control it finds from the collection.

panel1.Controls.RemoveByKey("notUnique");

You made the assumption that a control's name must be unique. But this is in fact not the case, and in the documentation for Control the Name property is not stated as having to be unique.

Now one could argue that as in the documentation the term "key" is used that this is a bug, but that is something to discuss with Microsoft! I suspect they will state it works as designed. (And I see a lot of broken code if they change it!)

Also interestingly, if you look at the method ControlCollection.Find

public System.Windows.Forms.Control[] Find (string key, bool searchAllChildren);

then you see that this will return an array.