Enumerators

  • Thread starter Thread starter Tim Davis
  • Start date Start date
T

Tim Davis

I am currently writing a class which I would like to make "enumerable" by
inheriting from IEnumerable. The documentation says that the
IEnumerator.MoveNext method should throw an exception if the object being
enumerated has changed since the enumerator was created. Does anyone have
any tips on how best to implement this?

I can think of 2 approaches, neither of which seem very satisfactory. These
are as follows:

1. Take a copy of the class being enumerated when the enumerator is created.
This would be a complete copy, not just a copy of the reference. Then, every
time MoveNext is called, it could compare the current contents to that when
the enumerator was created. This would have a potentially enormous overhead.

2. Have the class being enumerated keep a list of all currently live
enumerators. The class would then inform the enumerators if its contents
changed. This seems like a lot of work to implement something which you
might expect to be straightforward.

An alternative would be to not bother throwing this exception at all.
However this would then break the IEnumerable and IEnumerator specification
which might be considered bad practice.

Thanks,
Tim Davis
 
Tim Davis said:
I am currently writing a class which I would like to make "enumerable" by
inheriting from IEnumerable. The documentation says that the
IEnumerator.MoveNext method should throw an exception if the object being
enumerated has changed since the enumerator was created. Does anyone have
any tips on how best to implement this?

I can think of 2 approaches, neither of which seem very satisfactory.
These
are as follows:

Personally, I usually use a private version field in the object and change
the version on every modification. When you create an enumerator, you copy
the version value and compare your copy and the objects on every iteration,
if the version changes you have a changed collection.

Depending on your needs, you can probably either use a ulong and simply
increment each time or you could generate GUID's for each version. That
choice is yours.

For what its worth, you should be able to safely change an object without
causing IEnumerator to crash if and only if that change doesn't effect the
results IEnumerator returns. I don't think there is a reason to issue an
exception if some property unrelated to the enumerated contents is set.
 
Perhaps I'm missing the point, but IEnumerable classes are containers, like
ArrayList and Stack. We don't care if one of the objects within the
container is modified, only if the list itself has been modified (by adding,
deleting, or in some way reordering the list).

Therefore, in your class, set a "dirty" flag if any of these operations
occurs. Clear the "dirty" flag when the enumerator is initialized, and
check it on every MoveNext method call. If it has been set (by the code
adding an entry), throw the error.

Not difficult to implement.

--- Nick
 
Nick Malik said:
Perhaps I'm missing the point, but IEnumerable classes are containers,
like
ArrayList and Stack. We don't care if one of the objects within the
container is modified, only if the list itself has been modified (by
adding,
deleting, or in some way reordering the list).

Therefore, in your class, set a "dirty" flag if any of these operations
occurs. Clear the "dirty" flag when the enumerator is initialized, and
check it on every MoveNext method call. If it has been set (by the code
adding an entry), throw the error.

There is an issue here with an ordinary boolean dirty flag. Its possible
that a person could generate an IEnumerator, make a change, and generate
another IEnumerator which would reset the dirty flag and would then not
throw an exception when the first IEnumerator calls MoveNext(), even though
it should. Thats why I recommend a full version approach, every change
results in a unique version and every IEnumerator has the version it works
with. You don't have to worry about usage nuances like this. You do have to
consider the maximum enumerations, however, UInt64 allows you to have *ALOT*
of versions.
 
I don't think this would work if two enumerators were used at the same time.

Consider the following:
Enumerator1 is initialized and the dirty flag is set to clean.
MoveNext is called.
The IEnumerable derivative is then modified so the dirty flag is set to
"dirty".
Enumerator2 is initialized - the dirty flag is then reset to "clean".
When Enumerator1.MoveNext is called again, it wrongly thinks the IEnumerable
derivative is clean.

Regards,
Tim
 
Sounds like a good idea.

Thanks,
Tim

Daniel O'Connell said:
Personally, I usually use a private version field in the object and change
the version on every modification. When you create an enumerator, you copy
the version value and compare your copy and the objects on every iteration,
if the version changes you have a changed collection.

Depending on your needs, you can probably either use a ulong and simply
increment each time or you could generate GUID's for each version. That
choice is yours.

For what its worth, you should be able to safely change an object without
causing IEnumerator to crash if and only if that change doesn't effect the
results IEnumerator returns. I don't think there is a reason to issue an
exception if some property unrelated to the enumerated contents is set.
 
This actually is a point that gets very involved when you start dealing with
data binding, but I've found using a base class, or interface works quite
well. I just finished writing *ahem* super extended collection base class
from way over the mountain.

In it, I keep a guid that only changes when members are added or removed
from the collection. In additon, the objects it holds implement an
interface (I derived a new one from IEditableObject that adds an
EditStateChanged event). Any time an object in added to the collection, the
collection links on an event handlers and removes it when the item is
removed. The event is raised by the object when it 'changes' and that
causes the collection to also trigger is guid change. Then IEnumerable
implementation just stored the original guid and compares it the the one
currently in the collection before it does an operation. You could use and
other flagging type mechanism also.

If you were using structs in your collection, then you could do a hash on
the entire set and have a good idea that something has changed, but objects
dont change the hash because the default hash (i believe) only compares the
object references. All of this becomes big issues if and when you start
implementing the IBindingList interface.

Hope this helps, a little at least.
 
Back
Top