Обнаружение изменений с помощью IEnumerable

У меня есть вопрос, который я удивлен, еще не был задан именно в этом формате.

Если у меня есть IEnumerable, который генерируется на основе итерации по источнику данных (и с использованием оператора yield return), как я могу обнаружить, когда произошла модификация источника после доступа через Enumerator, сгенерированный через Вызов GetEnumerator?

Вот странная часть: я не многопоточен. Я думаю, что мой вопрос где-то имеет недостаток, потому что это должно быть просто.,, Я просто хочу знать, когда источник изменился и итератор устарел.

Огромное спасибо.

4 ответа

Решение

Вам необходимо самостоятельно обработать создание перечислителя, чтобы отслеживать эту информацию или при минимальном использовании. yield return; с вашим собственным типом отслеживания изменений на месте.

Например, большинство классов коллекции каркасов хранят номер версии. Когда они делают перечислитель, они сохраняют снимок этого номера версии и проверяют его во время MoveNext(), Вы можете сделать такую ​​же проверку перед звонком yield return XXX;

Большинство классов коллекций в.NET BCL использует атрибут версии для отслеживания изменений. То есть: перечислитель создается с номером версии (целое число) и проверяет, что исходный номер номера версии остается неизменным на каждой итерации (когда вызывается movenext). Коллекция, в свою очередь, увеличивает атрибут версии каждый раз, когда вносится изменение. Этот механизм отслеживания прост и эффективен.

2 других способа, которые я видел:

Наличие коллекции содержит внутреннюю коллекцию, содержащую слабые ссылки на выдающихся перечислителей. и каждый раз, когда в коллекцию вносятся изменения, она делает недействительным каждый перечислитель.

Или реализуйте события в коллекции ( INotifyCollectionChanged) и просто зарегистрируйте это событие в перечислителе. И если оно поднято, отметьте перечислитель как недействительный. Этот метод относительно прост в реализации, универсален и не требует больших затрат, но требует, чтобы ваша коллекция поддерживала события

Microsoft предлагает, чтобы любые изменения в коллекции IEnumerable приводили к аннулированию любых существующих объектов IEnumerator, но такая политика редко бывает особенно полезной и иногда может создавать неудобства. Нет никаких причин, по которым автору IEnumerable/IEnumerator необходимо создавать исключение, если коллекция изменена таким образом, чтобы не помешать IEnumerator возвращать те же данные, которые были бы возвращены без такой модификации. Я хотел бы пойти дальше и предложить, чтобы по возможности было желательно, чтобы перечислитель оставался функциональным, если он может подчиняться следующим ограничениям:

  1. Предметы, находящиеся в коллекции на протяжении всего времени перечисления, должны быть возвращены ровно один раз.
  2. Каждый элемент, который добавляется или удаляется во время перечисления, может быть возвращен ноль или один раз, но не более одного. Если объект удален из коллекции и повторно добавлен, его можно рассматривать как первоначально размещенный в одном элементе, но помещенный в новый, поэтому перечисление может законно вернуть старый, новый, оба или ни того, ни другого.

Класс VisualBasic.Collection ведет себя в соответствии с вышеуказанными ограничениями; такое поведение может быть очень полезным, делая возможным перечисление через класс и удаление элементов, отвечающих определенному критерию.

Конечно, разработка коллекции, которая будет вести себя разумно, если она изменена во время перечисления, не обязательно будет проще, чем создание исключения, но для коллекций разумного размера такая семантика может быть получена, если перечислитель преобразует коллекцию в список и перечисляет содержимое список. При желании, и особенно, если безопасность потоков не требуется, может быть полезно, чтобы коллекция сохраняла сильную или слабую ссылку на список, возвращаемый ее перечислителем, и аннулировала бы такую ​​ссылку каждый раз, когда она модифицируется. Другой вариант заключается в том, чтобы "реальная" ссылка на коллекцию содержалась в классе-обертке, и чтобы внутренний класс содержал счетчик количества существующих перечислителей (перечислители получат ссылку на реальную коллекцию). Если предпринята попытка изменить коллекцию, пока существуют перечислители, замените экземпляр коллекции на копию, а затем внесите в нее изменения (копия будет начинаться с нуля). Такой дизайн позволил бы избежать создания избыточных копий списка, за исключением случая, когда IEnumerator был оставлен без Dispose'd; даже в этом сценарии, в отличие от сценариев с WeakReferences или событиями, никакие объекты не будут поддерживаться дольше, чем это необходимо.

Я не нашел ответа, но в качестве обходного пути я только что поймал исключение, как это (пример WPF):

            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;
                }
            }
Другие вопросы по тегам