问题
I have a question that I am surprised hasn't already been asked in exactly this format.
If I have an IEnumerable that is generated based on iterating through a source of data, (and using a yield return statement), how can I detect when there has been a modification to the source after an access via an Enumerator that was generated via a GetEnumerator call?
Here is the strange part: I'm not multi-threading. I think my question has a flaw in it somewhere, because this should be simple. . . I just want to know when the source has changed and the iterator is out of date.
Thank you so much.
回答1:
You would need to handle creating the enumerator yourself in order to track this information, or, at a minimum use yield return;
with your own type of modification tracking in place.
Most of the framework collection classes, for example, keep a "version" number. When they make an enumerator, they keep a snapshot of that version number, and check it during MoveNext()
. You could make the same check before calling yield return XXX;
回答2:
Most collection classes in the .NET BCL uses a version attribute for change tracking. That is: the enumerator is constructed with a version number (integer) and checks the original source of the version number is still the same each iteration (when movenext is called). The collection in turn increments the version attribute each time a modification is made. This tracking mechanism is simple and effective.
2 other ways i've seen are:
Having the collection hold an internal collection containing weak references to outstanding enumerators. and each time a modification is made to the collection, it makes each enumerator which is still alive invalid.
Or implementing events in the collection ( INotifyCollectionChanged ) and simply register on that event in the enumerator. And if raised, mark the enumerator as invalid. This method is relatively easy to implement, generic and comes without to much overhead but requires your collection to support events
回答3:
Microsoft suggests any modification to an IEnumerable collection should void any existing IEnumerator objects, but that policy is seldom particularly helpful and can sometimes be a nuisance. There is no reason why the author of an IEnumerable/IEnumerator should feel a need to throw an exception if a collection is modified in a way that will not prevent the IEnumerator from returning the same data as it would have returned without such modification. I would go further and suggest that it should be considered desirable, when possible, to have an enumerator remain functional if it can obey the following constraints:
- Items which are in the collection throughout the duration of enumeration must be returned exactly once.
- Each items which is added or deleted during enumeration may be returned zero or one times, but no more than one. If an object is removed from the collection and re-added, it may be regarded as having been originally housed in one item but put into a new one, so enumeration may legitimately return the old one, the new one, both, or neither.
The VisualBasic.Collection class behaves according to the above constraints; such behavior can be very useful, making it possible to enumerate through the class and remove items meeting a particular criterion.
Of course, designing a collection to behave sensibly if it's modified during enumeration may not necessarily be easier than throwing an exception, but for collections of reasonable size such semantics may be obtained by having the enumerator convert the collection to a list and enumerate the contents of the list. If desired, and especially if thread safety is not required, it may be helpful to have the collection keep a strong or weak reference to the list returned by its enumerator, and void such reference any time it is modified. Another option would be to have a "real" reference to the collection be held in a wrapper class, and have the inner class keep a count of how many enumerators exist (enumerators would get a reference to the real collection). If an attempt is made to modify the collection while enumerators exist, replace the collection instance with a copy and then make the modifications on that (the copy would start with a reference count of zero). Such a design would avoid making redundant copies of the list except in the scenario where an IEnumerator is abandoned without being Dispose'd; even in that scenario, unlike scenarios involving WeakReferences or events, no objects would be kept alive any longer than necessary.
回答4:
I haven't found an answer, but as a work around I have just been catching the exception like this (WPF example):
while (slideShowOn)
{
if (this.Model.Images.Count < 1)
{
break;
}
var _bitmapsEnumerator = this.Model.Images.GetEnumerator();
try
{
while (_bitmapsEnumerator.MoveNext())
{
this.Model.SelectedImage = _bitmapsEnumerator.Current;
Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle, null);
Thread.Sleep(41);
}
}
catch (System.InvalidOperationException ex)
{
// Scratch this bit: the error message isn't restricted to English
// if (ex.Message == "Collection was modified; enumeration operation may not execute.")
// {
//
// }
// else throw ex;
}
}
来源:https://stackoverflow.com/questions/7396112/detecting-modifications-with-an-ienumerable