473,394 Members | 1,843 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,394 software developers and data experts.

Problem with GetEnumeration in Foreach loop

tlhintoq
3,525 Expert 2GB
Situation:
A Windows Form with a single User Control containing 4 custom controls. This lets me drop the User Control on its own form to make it a dialog, or make it part of a larger configuration dialog with several such User Controls.
Each custom control has a property for COM port name (serial port).
Each custom control has a method for writing the settings.
The user control has a GetEnumeration structure so it can be easily referenced with a foreach (myCustomControl Bob in myUserControl) type reference.

Errant behavior:
If I walk through using the foreach construct everything executes correctly THEN all the custom controls are inexplicably rendered invisible. They still exist because I can step through the save function and they all perform their methods. They just become hidden on the Windows form.
Yet - If I walk through the same steps with a for (int a = [...]) type construct doing exactly the same things everything executes and the custom controls are NOT hidden.

Its not like the loop is doing a thousand tasks and the process is hard to follow:
1) Set port name
2) Write settings

It really shouldn't matter *how* the set of controls is looped through. Both the 'for' and 'foreach' *should* being doing exactly the same thing: Loop through the four controls, set the port name property and execute the WritePrefs() method. That's it. Yet for some reason after the 'foreach' completes all the custom controls go invisible.

Something in the bowels of the foreach seems to be reacting to the custom controls .Show() and .Hide()

Does anyone have a clue or has anyone seen something like this before?
Obviously I have already worked around the weird behavior by using a 'for' instead of 'foreach'. But I'd like to understand the actual problem so I can use the more elegant 'foreach' in the future.

The foreach that hides the custom controls
Expand|Select|Wrap|Line Numbers
  1.             foreach (Saint.Controls.Devices.DeviceCtrl pebbles in Controller1)
  2.             {
  3.                 pebbles.myObject.PortName = myCOMport.PortName;
  4.                 pebbles.myObject.WritePrefs();
  5.             }
  6.  
The for that does the same thing without hiding the custom controls.
Expand|Select|Wrap|Line Numbers
  1.             for (int a = 0; a < Controller1.Controls.Count; a++)
  2.             {
  3.                 ((Saint.Controls.Devices.DeviceCtrl) Controller1.Controls[a]).myObject.PortName = myCOMport.PortName;
  4.                 ((Saint.Controls.Devices.DeviceCtrl) Controller1.Controls[a]).myObject.WritePrefs();
  5.             }
  6.  
The class with the GetEnumeration
Expand|Select|Wrap|Line Numbers
  1.     public partial class Controller1: UserControl, IEnumerable, IEnumerator
  2.     {
  3.         ArrayList AllControls = new ArrayList();
  4.  
  5.         #region GetEnumeration
  6.         int Position = -1;
  7.  
  8.         public IEnumerator GetEnumerator()
  9.         {
  10.             return (IEnumerator)this;
  11.         }
  12.         public bool MoveNext()
  13.         {
  14.             if (Position < AllControls.Count - 1)
  15.             {
  16.                 ++Position;
  17.                 return true;
  18.             }
  19.             Reset();
  20.             return false;
  21.         }
  22.         public void Reset()
  23.         {
  24.             Position = -1;
  25.         }
  26.         public object Current
  27.         {
  28.             get
  29.             {
  30.                 return AllControls[Position];
  31.             }
  32.         }
  33.         #endregion GetEnumeration
  34.  
  35.  
  36.         #region Construction
  37.         public Controller1()
  38.         {
  39.             InitializeComponent();
  40.             AllControls.Add(tcntrlWiper);
  41.             AllControls.Add(tcntlWasher);
  42.             AllControls.Add(tcntrlBlower);
  43.             AllControls.Add(tcntrlDefroster);
  44.         }
  45.         #endregion Construction
  46.     }
  47.  
Feb 5 '09 #1
9 2544
vekipeki
229 Expert 100+
When controls are hidden but still alive, it means they have been removed from the Control.Controls collection. But I don't see what could do that from your code (when comparing 'for' with 'foreach').

The strangest thing is that you don't even have to access your controls to have them removed from Controls collection, try this:

Expand|Select|Wrap|Line Numbers
  1. foreach (Control c in Controller1)
  2. {
  3.     // this will show 4
  4.     Console.WriteLine(Controller1.Controls.Count);
  5.  
  6.     // break immediately after getting the 1st control
  7.     break;
  8. }
  9. // this will write 0
  10. Console.WriteLine(Controller1.Controls.Count);
  11.  
That's pretty weird!

You can use foreach directly on Controller1.Controls collection, instead of implementing IEnumerable, or implement IEnumerable just as a wrapper for Controls.GetEnumerator():

Expand|Select|Wrap|Line Numbers
  1. public IEnumerator GetEnumerator()
  2. {
  3.     return this.Controls.GetEnumerator();
  4. }
  5.  
(this last part doesn't solve the actual problem, but it works this way)
Feb 5 '09 #2
tlhintoq
3,525 Expert 2GB
I'm not convinced the controls are being removed - only because if I run the function a second time and step through it still loops four times receiving the Wiper, Washer, Blower, Defroster controls. If they were actually being removed the the controls collection after the first run of the function, wouldn't the second run have no controls to loop through?
Feb 5 '09 #3
Plater
7,872 Expert 4TB
Both you made your foreach loop in the same format:
foreach (Control c in Controller1)

If I were to have used the loop, I would have said
foreach (Control c in Controller1.Controls)

Not sure it matters, but I find it interesting that you don't actually reference your exact collection object?
Feb 5 '09 #4
tlhintoq
3,525 Expert 2GB
There was actually a reason for doing this way. My GetEnumeration is referencing only an array of custom controls. Right now that array is exactly the same as the controls collection. But I try to plan ahead and could see a time where the controls collection might include lables, pictureboxes, group boxes etc., yet I would want the foreach to only return the significant custom controls and not each and every little piece of decoration added to the User Control panel. Or maybe I only want to perform a function on *some* custom controls like controllable devices, but not all custom controls such as readout only sensors... So it made sense that I should be able to make a list of the objects that I want to be included in the enumeration and ignore others.

After some experimentation I found some more interesting results...
This works without hiding the controls... just as was suggested... by adding the reference to the .Controls collection.
Expand|Select|Wrap|Line Numbers
  1.             foreach (Saint.Controls.Devices.DeviceCtrl pebbles in Controller1.Controls)
  2.             {
  3.                 Console.WriteLine("TCCC: " + Controller1.Controls.Count);
  4.                 pebbles.myObject.PortName = myCOMport.PortName;
  5.                 pebbles.myObject.WritePrefs();
  6.             }
  7.  
So I figured Okay, I'll adjust the GetEnumeration to work with the controls collection to see what happens. It should be exactly the same since I am referencing the same array. So I comment out all the reference to my array and change lines 14 & 30 to directly reference the .Controls.

Expand|Select|Wrap|Line Numbers
  1.     public partial class Controller1: UserControl, IEnumerable, IEnumerator
  2.     {
  3.         //ArrayList AllControls = new ArrayList();
  4.  
  5.         #region GetEnumeration
  6.         int Position = -1;
  7.  
  8.         public IEnumerator GetEnumerator()
  9.         {
  10.             return (IEnumerator)this;
  11.         }
  12.         public bool MoveNext()
  13.         {
  14.             if (Position < this.Controls.Count - 1)
  15.             {
  16.                 ++Position;
  17.                 return true;
  18.             }
  19.             Reset();
  20.             return false;
  21.         }
  22.         public void Reset()
  23.         {
  24.             Position = -1;
  25.         }
  26.         public object Current
  27.         {
  28.             get
  29.             {
  30.                 return this.Controls[Position];
  31.             }
  32.         }
  33.         #endregion GetEnumeration
  34.  
  35.  
  36.         #region Construction
  37.         public ThrillControllerCntrl()
  38.         {
  39.             InitializeComponent();
  40.             //AllControls.Add(tcntrlWiper);
  41.             //AllControls.Add(tcntlWasher);
  42.             //AllControls.Add(tcntrlBlower);
  43.             //AllControls.Add(tcntrlDefroster);
  44.         }
  45.         #endregion Construction
  46.     }
  47.  
  48.  
Now that should have worked whether my foreach loop referenced the .Controls collection or not, right? - NOPE - It still hides the custom controls upon completion if I don't specify the .Collection in the foreach call construct.
-BUT- This does now remove the control from the collection: The second time through the function the collection count becomes zero.

As that weird or what?

Just to clarify one other thing... I got thinking that if I added a label(or something else) to the panel that maybe it wouldn't be a problem since the "foreach" call does specify an exact custom type. -NOPE- All controls of all types are returned and the code breaks as soon as it tries to cast a label to my custom type. - Which I had expected and why I wanted my GetEnumeration to only return exactly what I specified so I didn't have to filter at every point I used the call.
Expand|Select|Wrap|Line Numbers
  1. Unable to cast object of type 'System.Windows.Forms.Label' to type 'Saint.Controls.Devices.DeviceCtrl'.
  2.  
Seems rather limiting, but I can work around it - for now. I would have expected that my class should be able to specify what it will and won't return via my GetEnumeration method.
Feb 5 '09 #5
vekipeki
229 Expert 100+
That's my point exactly, your controls are removed from the Controls collection, not AllControls collection. Forms' controls are drawn only if they are part of the Controls collection.

Actually, your controls are removed from the Controls collection if you just implement IEnumerator, regardless of what it actually accesses -- check this out:

Expand|Select|Wrap|Line Numbers
  1. #region IEnumerable
  2.  
  3. public IEnumerator GetEnumerator()
  4. {
  5.  return this;
  6. }
  7.  
  8. #endregion
  9.  
  10. #region IEnumerator
  11.  
  12. public object Current
  13. {
  14.     get { return new object(); } // we're not accessing anything
  15. }
  16.  
  17. public bool MoveNext()
  18. {
  19.     return false; // never enter the foreach loop
  20. }
  21.  
  22. public void Reset()
  23. {
  24.     // do nothing
  25. }
  26.  
  27. #endregion
It still does the same thing! Only because you implemented that interface, regardless of the fact that MoveNext returns false, and the foreach loop is never entered!

To make it even stranger, you can completely remove AllControls list to see that it is doesn't make any difference - it's only the IEnumerator!

But, this will work, and will allow you to have a custom collection:

Expand|Select|Wrap|Line Numbers
  1. public IEnumerator GetEnumerator()
  2. {
  3.  return AllControls.GetEnumerator();
  4. }
  5.  
  6. // implementing IEnumerator is no longer needed
  7.  
Feb 6 '09 #6
tlhintoq
3,525 Expert 2GB
I am about to jump on a plane to Dallas for an installation. As soon as I get back next week I will play with this. Thank you very much for the code. I will pour over exactly how it works so that I can use it to just like the custom controls. I never like having code that works if I don't actually UNDERSTAND the why behind it. I'm guessing that I just made it more complicated than it needed to be; assuming I had to do more by hand than needed.
Feb 6 '09 #7
tlhintoq
3,525 Expert 2GB
That's my point exactly, your controls are removed from the Controls collection, not AllControls collection. Forms' controls are drawn only if they are part of the Controls collection.
I would still like to understand why and where they are being removed *at all*. I totally understand that the form can't display items if they aren't in the item's collection. But why or where is the enumeration process removing them from the collection?
Feb 6 '09 #8
Plater
7,872 Expert 4TB
Perhaps your enumerator screws up the pointers to the collection object's natural pointer.
Like if your objects have internal enumeration pointers, and the collection object has one that points to those pointers....when you make your own enumeration it changes the pointers and the collection pointer now points to something that isn't there, it assumes it must be empty.

Like if you had a linked list with X amount of items, and removed the first item in the list, but didn't update the HEAD pointer first, the HEAD pointer would be looking at garbage and not at a list member anymore, so a traversal would appear empty?

That might not make sense to anyone else, but seems plausable to me.
Feb 6 '09 #9
vekipeki
229 Expert 100+
I just used ildasm to look at the IL for a small app, which called foreach on a control implementing IEnumerator.

And there was the answer: right after foreach ends, there is a check to see if the returned IEnumerator implements IDisposable, and if yes, it calls Dispose() to dispose it. To be sure, I added a breakpoint in the overriden Dispose(bool disposing) method and it was really called right after foreach.

So, in other words, CLR disposes the enumerator after use, but the enumerator is actually your control instance.

I guess this is why they always implement IEnumerator in a private class and instantiate on every call to GetEnumerator - so it can be collected immediately! :)
Feb 8 '09 #10

Sign in to post your reply or Sign up for a free account.

Similar topics

3
by: Paul Porcelli | last post by:
I have the following code(excerpt) which grabs some lines from a syslog file and adds any found in the range to an array. @lines=();@vvlines=(); $t = new Net::Telnet (Timeout => 30, Prompt =>...
104
by: cody | last post by:
What about an enhancement of foreach loops which allows a syntax like that: foeach(int i in 1..10) { } // forward foeach(int i in 99..2) { } // backwards foeach(char c in 'a'..'z') { } // chars...
32
by: Joe Rattz | last post by:
Hmmm, I wrote the following code. I want an array of bools and I want to intialize them to false. bool bits = new bool; foreach(bool bit in bits) { bit = false; } The compiler complains...
15
by: Mike Lansdaal | last post by:
I came across a reference on a web site (http://www.personalmicrocosms.com/html/dotnettips.html#richtextbox_lines ) that said to speed up access to a rich text box's lines that you needed to use a...
13
by: TrintCSD | last post by:
How can I reset the collections within a foreach to be read as a change from within the foreach loop then restart the foreach after collections has been changed? foreach(string invoice in...
8
by: Dave Hagerich | last post by:
I'm using a DataGrid with a DataSet and I'm trying to filter the data being displayed, using the following code as a test: DataView theView = new DataView(theDataSet.Tables); theView.RowFilter =...
4
by: Sjoerd | last post by:
Summary: Use foreach(..) instead of while(list(..)=each(..)). --==-- Foreach is a language construct, meant for looping through arrays. There are two syntaxes; the second is a minor but useful...
3
by: Akira | last post by:
I noticed that using foreach is much slower than using for-loop, so I want to change our current code from foreach to for-loop. But I can't figure out how. Could someone help me please? Current...
2
by: recordlovelife | last post by:
So I am trying to display a title, date, and content of a wordpress blog. Word press provides nice drop in functions to get the job done with simple names like "the_title", and the "the_content" But...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.