-2

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?

HiImEins
  • 42
  • 4
  • 2
    Why do you say they have the same index? This question is confusing. – ProgrammingLlama Aug 20 '21 at 01:53
  • I mean, the class name can act as index of the collection, because Controls["UserControl"] in the last line return uc1. As uc1 and uc2 are 2 instances of the same class and they are both in the collection Controls, can we say that they have the same index? – HiImEins Aug 20 '21 at 02:00
  • In the case of a non-unique _key_ (which in this case is the .Name property of the control, if I'm not mistaken), I would expect that it just takes the first item. Numerically they will definitely have different indexes. – ProgrammingLlama Aug 20 '21 at 02:02

2 Answers2

2

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.

jason.kaisersmith
  • 8,712
  • 3
  • 29
  • 51
  • 1
    Could it be that this behaviour is very old and has its origins pre .Net? Under previous versions of Visual Basic, you could give the same Name to one or more controls and VB would automatically create an array. It was quite a nice facility, enabling you to iterate through controls (for example assigning properties at runtime). – Jonathan Willcock Aug 20 '21 at 05:20
  • I rather assumed that the index or key of a element in collection must be unique, even if they are string. Thank you for the answer! – HiImEins Aug 20 '21 at 12:54
0

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..

Caius Jard
  • 72,509
  • 5
  • 49
  • 80