473,386 Members | 1,715 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,386 software developers and data experts.

Scope of IEnumerable object within foreach

This is partly 'for the record' and partly a query about whether the
following is a bug somewhere in .Net (whether it be the CLR, JITter, C#
compiler). This is all in the context of .Net 1.1 SP1.
Recently we (my fellow team members & I) observed an
InvalidOperationException - 'collection has been modified', however it
wasn't immediately obvious why this would be occuring. There are no
modifications occuring within the loop and there is not another thread
that could be making a change.

The object being looped over was derived from CollectionBase and the
culprit turned out to be a finalizer on the class, a finalizer that
called Clear() thus modifying the collection. My question then is, why
did the finalizer get called while the CollectionBase object was still
in scope? Or more to the point, the object has clearly gone out of
scope and so should it have done?

Here's some code to demonstrate the issue fairly reliably (on my PC)...

---------------------------------------------
// Our problematic CollectionBase class.
public class TestCollection : CollectionBase
{
public void Add(object o)
{
List.Add(o);
}

~TestCollection()
{
this.Clear();
}
}

// A helper class. This has no real relevance to the problem.
public class ListItem
{
private string itemCode;
private string itemDescription;

public ListItem(string itemCode, string itemDescription)
{
this.itemCode = itemCode;
this.itemDescription = itemDescription;
}

public override string ToString()
{
return itemDescription;
}
}
---------------------------------------------

If you then create a form in Visual Studio, drop a ComboBox and a
button on there and add the following code:

--------------------------------------------
// Creates an instance of TestCollection with some ListItems(see above)
in it.
private TestCollection BuildCol(int size)
{
TestCollection oList = new TestCollection();

for(int i=0; i<size; i++)
{
string s = i.ToString();
oList.Add(new ListItem(s,s));
}

return oList;
}

// Populates our combobox.
private void PopulateCombobox()
{
TestCollection col = BuildCol(1000);

foreach(object o in col) // exception thrown here!
{
comboBox1.Items.Add(o);

}

comboBox1.Sorted = true;
}

// From the button click event we populate the combobox several times
over to
// increase the chances of reproducing the problem.
private void button1_Click(object sender, System.EventArgs e)
{
for(int i=0; i<100; i++)
{
PopulateCombobox();
comboBox1.Items.Clear();
}
}
--------------------------------------------
Running this code generates an exception on every clicks of the button
here. If you don;t get this then you can add a call to GC.Collect()
within the foreach loop to cause a garbage collection which in turn
will invoke the TestCollection's finalizer.

What I think is happening here is that foreach is just shorthand for
something like:

--------------
IEnumerator e = col.GetEnumerator();
while(e.Current!=null)
{
// Do stuff here.

e.MoveNext();
}
--------------

So although technically 'col' is in scope for the lifetime of the loop,
in reality a code optimizer may be flagging it as out of scope since it
is not actually being used within the loop. If a garbage collection
happens to occur then the finalizer is called, modifying the collection
and bang! Of course the IEnumerator would normally have a reference to
the collection so this really doesn't seem right to me.

Any thoughts?

Colin Green

Nov 25 '05 #1
2 3177
<bu*******@hotmail.com> wrote:
This is partly 'for the record' and partly a query about whether the
following is a bug somewhere in .Net (whether it be the CLR, JITter, C#
compiler). This is all in the context of .Net 1.1 SP1.
Recently we (my fellow team members & I) observed an
InvalidOperationException - 'collection has been modified', however it
wasn't immediately obvious why this would be occuring. There are no
modifications occuring within the loop and there is not another thread
that could be making a change.

The object being looped over was derived from CollectionBase and the
culprit turned out to be a finalizer on the class, a finalizer that
called Clear() thus modifying the collection.
Did you really need the finalizer in the first place? Very, very few
classes really need finalizers.
My question then is, why
did the finalizer get called while the CollectionBase object was still
in scope? Or more to the point, the object has clearly gone out of
scope and so should it have done?
Let's be clear about things:
1) Objects don't have scope
2) Variables have scope
3) Scope *in itself* doesn't entirely govern whether or not a variable
prevents the object it refers to from being garbage collected

<snip>

Variables don't prevent an object from being garbage collected when the
JIT can tell that the variable's value isn't used again. For instance,
if you do:

object o = new object();

Thread.Sleep(1000); // During this sleep, the first object is eligible
// for garbage collection

o = new object();
What I think is happening here is that foreach is just shorthand for
something like:

--------------
IEnumerator e = col.GetEnumerator();
while(e.Current!=null)
{
// Do stuff here.

e.MoveNext();
}
--------------
Indeed.
So although technically 'col' is in scope for the lifetime of the loop,
in reality a code optimizer may be flagging it as out of scope since it
is not actually being used within the loop. If a garbage collection
happens to occur then the finalizer is called, modifying the collection
and bang! Of course the IEnumerator would normally have a reference to
the collection so this really doesn't seem right to me.


In the case of CollectionBase, however, the enumerator doesn't need to
have a reference to the CollectionBase itself - just the "inner list"
it contains.

So, your CollectionBase was genuinely being finalized, although the
"inner list" it used was still in use. Unfortunately, your rogue
finalizer cleared the list, hence the exception you saw.

--
Jon Skeet - <sk***@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Nov 25 '05 #2
Hi Jon,

Thanks for the clarification. There was an air of mystery surrounding
this bug for a while so it's nice to fully understand what is going on.
It's worth noting that at least one other person here working on a
seperate project had also come across the same issue and had just
marked it as a bug in dotnet, using workarounds to solve the problem
such as lock statements, GC.KeepAlive() and avoiding the foreach
statement. This makes me wonder if there are others out there that have
made the same assumption and who now just avoid using foreach.

Cheers,

Colin Green

Nov 29 '05 #3

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

2
by: Gordon Rundle | last post by:
It drives me nuts that I can't use foreach with an enumerator instance. I would like the following to be functionally identical: foreach (Object o in MyCollection) ... foreach (Object o in...
1
by: Maziar Aflatoun | last post by:
Hi everyone, I'm having a problem with reading user groups on Active Directory using C#. It returns all the groups in the Universal scope for a specific user. However, I only need the groups in...
10
by: jcc | last post by:
Hi guys, I'm a newbie to C#. My Visual Studio 2005 failed to compile the following code with error as 'HelloWorld.A' does not implement interface member...
5
by: Tin Gherdanarra | last post by:
Dear mpdls, here is a simple example of an IEnumerable that generates integers: It works, but I have only a vague idea of what's going on. I understand that /yield/ wraps the humble integer...
5
by: strawberry | last post by:
In the function below, I'd like to extend the scope of the $table variable such that, once assigned it would become available to other parts of the function. I thought 'global $table;' would solve...
2
by: =?Utf-8?B?a2VubmV0aEBub3NwYW0ubm9zcGFt?= | last post by:
When creating multiple iterators, the original is defined as returning IEnumerator, ie public IEnumerator GetEnumerator() { yield x; ...} whereas the additional ones are defined as returning...
6
by: timor.super | last post by:
Hi group, imagine I want to count the number of a word in a text. See my actual code (don't pay attention to the int factor) : List<KeyValuePair<string, int>listThingsToFind = new...
2
by: Ronald S. Cook | last post by:
Does anyone know how to convert an object of type IEnumerable to a DataTable? Thanks, Ron
4
by: jmDesktop | last post by:
In the code below from MSDN How do the PeopleEnum methods ever get called? foreach (Person p in peopleList) Console.WriteLine(p.firstName + " " + p.lastName); What is going on behind the...
0
by: Charles Arthur | last post by:
How do i turn on java script on a villaon, callus and itel keypad mobile phone
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
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
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...

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.