Коллекция была изменена; операция перечисления может не выполняться

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

Коллекция была изменена; операция перечисления может не выполняться

Ниже приведен код.

Это WCF-сервер в службе Windows. Метод NotifySubscribers() вызывается службой всякий раз, когда происходит событие данных (через случайные интервалы, но не очень часто - примерно 800 раз в день).

Когда клиент Windows Forms подписывается, идентификатор подписчика добавляется в словарь подписчиков, а когда клиент отказывается от подписки, он удаляется из словаря. Ошибка возникает, когда (или после) клиент отписывается. Похоже, что при следующем вызове метода NotifySubscribers() цикл foreach() завершится ошибкой с ошибкой в ​​строке темы. Метод записывает ошибку в журнал приложения, как показано в приведенном ниже коде. Когда отладчик присоединен и клиент отписывается, код выполняется нормально.

Вы видите проблему с этим кодом? Нужно ли мне делать словарь поточно-ориентированным?

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class SubscriptionServer : ISubscriptionServer
{
    private static IDictionary<Guid, Subscriber> subscribers;

    public SubscriptionServer()
    {            
        subscribers = new Dictionary<Guid, Subscriber>();
    }

    public void NotifySubscribers(DataRecord sr)
    {
        foreach(Subscriber s in subscribers.Values)
        {
            try
            {
                s.Callback.SignalData(sr);
            }
            catch (Exception e)
            {
                DCS.WriteToApplicationLog(e.Message, 
                  System.Diagnostics.EventLogEntryType.Error);

                UnsubscribeEvent(s.ClientId);
            }
        }
    }
    
    public Guid SubscribeEvent(string clientDescription)
    {
        Subscriber subscriber = new Subscriber();
        subscriber.Callback = OperationContext.Current.
                GetCallbackChannel<IDCSCallback>();

        subscribers.Add(subscriber.ClientId, subscriber);
        
        return subscriber.ClientId;
    }

    public void UnsubscribeEvent(Guid clientId)
    {
        try
        {
            subscribers.Remove(clientId);
        }
        catch(Exception e)
        {
            System.Diagnostics.Debug.WriteLine("Unsubscribe Error " + 
                    e.Message);
        }
    }
}

person cdonner    schedule 03.03.2009    source источник
comment
в моем случае это был побочный эффект, потому что я использовал несколько .Include (table), которые были изменены во время процесса - не очень очевидно при чтении кода. однако мне повезло, что эти включения не нужны (да! старый, неподдерживаемый код), и я решил свою проблему, просто удалив их   -  person Adi    schedule 10.03.2020
comment
Взгляните на ответ, предоставленный @joe. Во многих случаях это гораздо лучшее решение. stackoverflow.com/a/57799537/10307728   -  person orangecaterpillar    schedule 26.07.2020


Ответы (17)


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

foreach(Subscriber s in subscribers.Values)

To

foreach(Subscriber s in subscribers.Values.ToList())

Если я прав, проблема исчезнет.

Вызов subscribers.Values.ToList() копирует значения subscribers.Values в отдельный список в начале foreach. Ни у кого другого нет доступа к этому списку (у него даже нет имени переменной!), Поэтому ничто не может изменить его внутри цикла.

person JaredPar    schedule 03.03.2009
comment
Кстати .ToList () присутствует в dll System.Core, которая несовместима с приложениями .NET 2.0. Поэтому вам может потребоваться изменить целевое приложение на .Net 3.5 - person mishal153; 19.05.2010
comment
Отлично. Я сделал это с помощью ArrayList, и он также отлично работал (очевидно, с ToArray ()) - person aleafonso; 19.09.2011
comment
Я не понимаю, почему вы сделали ToList и почему это все исправляет - person PositiveGuy; 29.02.2012
comment
@CoffeeAddict: проблема в том, что subscribers.Values изменяется внутри цикла foreach. Вызов subscribers.Values.ToList() копирует значения subscribers.Values в отдельный список в начале foreach. Ни у кого другого нет доступа к этому списку (у него даже нет имени переменной!), поэтому ничто не может изменить его внутри цикла. - person BlueRaja - Danny Pflughoeft; 20.04.2012
comment
@ BlueRaja-DannyPflughoeft, значит, пока ToList не применяется, мы работаем со ссылками на тот же список? Даже если мы уже начали перебирать новый список? - person Johnny_D; 30.04.2014
comment
Обратите внимание, что ToList также может выбрасывать, если коллекция была изменена во время выполнения ToList. - person Sriram Sakthivel; 15.05.2015
comment
@Zapnologica У вас не будет нулевого указателя, потому что C # - это управляемый код. Экземпляр будет удален сборщиком мусора только после того, как будут освобождены все ссылки. То, что вы говорите, было бы правдой, если бы это был C ++. Но я согласен, это может привести к несоответствиям, потому что вы можете работать с устаревшим перечислением. - person Vishnu Pedireddi; 10.06.2015
comment
Я почти уверен, что это не решает проблему, а просто затрудняет ее воспроизведение. ToList не является атомарной операцией. Что еще забавнее, ToList в основном делает свое собственное foreach внутреннее копирование элементов в новый экземпляр списка, то есть вы решили foreach проблему, добавив дополнительную (хотя и более быструю) foreach итерацию. - person Groo; 15.06.2015
comment
Я думаю, что опасения по поводу того, что ToList не является атомарным, очень странны. Это не многопоточный код. Думать, что цикл может начаться до завершения ToList, в корне неверно понимать, как работают языки программирования. Он не будет выполнять произвольные многопоточные части вашего кода или выполнять код не по порядку. Кроме того, InstanceContextMode.Single обеспечивает одновременную обработку только одного запроса. - person KthProg; 27.01.2017
comment
@SriramSakthivel, как насчет использования ToArray ()? - person Kugan Kumar; 27.06.2017
comment
@KuganKumar ToArray ничем не отличается от ToList. Он также вызовет исключение, если будет изменен в другом потоке при выполнении метода ToArray. - person Sriram Sakthivel; 28.06.2017
comment
Возникла эта проблема при просмотре словаря элементов и добавлении промежуточного потока, и вышеуказанный метод .ToList() исправил это. - person Mike Upjohn; 04.09.2017
comment
Что, если вы столкнетесь с этой проблемой в коллекции, которая уже является списком? - person NickG; 25.10.2017
comment
Чего бы это ни стоило ... плакат не сказал, что это решит проблему ... Он сказал, что его можно использовать для проверки / подтверждения проблемы - person Ben Call; 28.09.2018
comment
@NickG Это не имеет значения, поскольку .ToList() всегда будет создавать новый список, независимо от фактического типа перечисляемого, в котором вы его используете. - person Tom Lint; 27.08.2019
comment
@KuganKumar, поскольку @SriramSakthivel сказал, что такие методы, как ToList ToArray, по-прежнему должны будут просматривать список, поэтому они все равно могут использовать параллельный сценарий. Причина, по которой это наиболее популярное решение кажется работающим, заключается в том, что, в отличие от foreach, ToList не требует выполнения каких-либо процессов, оно намного быстрее. Но маловероятно не означает детерминированный. см. этот пример. Решением этого является использование неизменяемых классов. Этот ответ нужно улучшить. - person joe; 05.09.2019
comment
Будьте осторожны с исправлением ошибки, это может быть более серьезной проблемой. Я использую собственный сайт nopcommerce, и в копии из службы продукта возникла ошибка. это произошло потому, что я редактировал продукт, а затем пытался скопировать из того же продукта в себя. В этой ситуации вы не хотите этого допускать, поэтому, возможно, это какая-то удобная для пользователя ошибка, объясняющая пользователю, что он не может использовать копию для копирования продукта, который они редактируют, в себя :) - person Web Dev Guy; 01.04.2020
comment
@NickG, вы можете использовать ToArray() или сделать копию (явно) списка, прежде чем запускать цикл и перебирать копию. - person orangecaterpillar; 26.07.2020
comment
Также посмотрите ответ @joe ниже, используя неизменяемые типы. stackoverflow.com/a/57799537/10307728 - person orangecaterpillar; 26.07.2020
comment
В моем случае объекты обновлялись и сохранялись в базе данных, но мне было интересно узнать об ошибке / предупреждении, которое я получал вместо 200. Спасибо, это сработало отлично. - person Spyros_Spy; 21.10.2020
comment
Сработало для меня, хотя, как отмечали другие: возможно, немного неэффективно - для тех, кто задается вопросом, почему это работает: создается локальная копия, а затем повторяется, останавливает любые другие обновления, мешающие ей, поскольку к ней обращаются только в локальной области. - person Jamie Nicholl-Shelley; 16.11.2020
comment
это работает для меня - person Raj K; 11.03.2021
comment
@JaredPar спасибо за предложение, это сработало! - person Artorias2718; 30.06.2021

Когда подписчик отказывается от подписки, вы изменяете содержимое коллекции подписчиков во время перечисления.

Есть несколько способов исправить это, один из них - изменить цикл for для использования явного .ToList():

public void NotifySubscribers(DataRecord sr)  
{
    foreach(Subscriber s in subscribers.Values.ToList())
    {
                                              ^^^^^^^^^  
        ...
person Mitch Wheat    schedule 03.03.2009

На мой взгляд, более эффективный способ - иметь другой список, в который вы заявляете, что помещаете в него все, что «подлежит удалению». Затем после завершения основного цикла (без .ToList ()) вы выполняете еще один цикл над списком «подлежащих удалению», удаляя каждую запись по мере ее появления. Итак, в своем классе вы добавляете:

private List<Guid> toBeRemoved = new List<Guid>();

Затем вы меняете его на:

public void NotifySubscribers(DataRecord sr)
{
    toBeRemoved.Clear();

    ...your unchanged code skipped...

   foreach ( Guid clientId in toBeRemoved )
   {
        try
        {
            subscribers.Remove(clientId);
        }
        catch(Exception e)
        {
            System.Diagnostics.Debug.WriteLine("Unsubscribe Error " + 
                e.Message);
        }
   }
}

...your unchanged code skipped...

public void UnsubscribeEvent(Guid clientId)
{
    toBeRemoved.Add( clientId );
}

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

person x4000    schedule 03.03.2009
comment
Я ожидаю, что об этом стоит подумать, если вы работаете с более крупными коллекциями. Если бы он был маленьким, я бы, вероятно, просто ToList и двинулся дальше. - person Karl Kieninger; 27.03.2015

Вы также можете заблокировать словарь подписчиков, чтобы предотвратить его изменение при зацикливании:

 lock (subscribers)
 {
         foreach (var subscriber in subscribers)
         {
               //do something
         }
 }
person Mohammad Sepahvand    schedule 23.05.2012
comment
Это полный пример? У меня есть класс (_dictionary obj ниже), который содержит общую строку Dictionary ‹, int› с именем MarkerFrequencies, но это не помогло мгновенно устранить сбой: lock (_dictionary.MarkerFrequencies) {foreach (KeyValuePair string ‹, int› пара в _dictionary.MarkerFrequencies) {...}} - person Jon Coombs; 21.04.2014
comment
@JCoombs, возможно, вы изменяете, возможно, переназначаете словарь MarkerFrequencies внутри самой блокировки, что означает, что исходный экземпляр больше не заблокирован. Также попробуйте использовать for вместо foreach, см. это и this. Дайте мне знать, если это решит проблему. - person Mohammad Sepahvand; 21.04.2014
comment
Что ж, я наконец-то добрался до исправления этой ошибки, и эти советы были мне полезны - спасибо! Мой набор данных был небольшим, и эта операция была просто обновлением отображения пользовательского интерфейса, поэтому повторение копии казалось лучшим вариантом. (Я также экспериментировал с использованием for вместо foreach после блокировки MarkerFrequencies в обоих потоках. Это предотвратило сбой, но, похоже, потребовало дальнейшей работы по отладке. И это внесло больше сложности. Моя единственная причина наличия здесь двух потоков - дать пользователю возможность отменить операция, между прочим. Пользовательский интерфейс не изменяет эти данные напрямую.) - person Jon Coombs; 22.07.2014
comment
Проблема в том, что для больших приложений блокировки могут серьезно снижать производительность - лучше использовать коллекцию в пространстве имен System.Collections.Concurrent. - person BrainSlugs83; 08.09.2014

Почему эта ошибка?

Как правило, коллекции .Net не поддерживают одновременное перечисление и изменение. Если вы попытаетесь изменить список коллекции во время перечисления, возникнет исключение. Итак, проблема, стоящая за этой ошибкой, заключается в том, что мы не можем изменить список / словарь, пока мы просматриваем то же самое.

Одно из решений

Если мы выполняем итерацию по словарю, используя список его ключей, параллельно мы можем изменять объект словаря, поскольку мы выполняем итерацию по коллекции ключей, а не по словарю (и повторяем его коллекцию ключей).

Пример

//get key collection from dictionary into a list to loop through
List<int> keys = new List<int>(Dictionary.Keys);

// iterating key collection using a simple for-each loop
foreach (int key in keys)
{
  // Now we can perform any modification with values of the dictionary.
  Dictionary[key] = Dictionary[key] - 1;
}

Вот блог опубликовать об этом решении.

И для более глубокого изучения StackOverflow: Почему возникает эта ошибка?

person Shri    schedule 11.11.2014
comment
Спасибо! Четкое объяснение того, почему это происходит, и сразу же дало мне возможность решить эту проблему в моем приложении. - person Kim Crosser; 14.09.2019
comment
Начиная с .NET Core 3.0 с C # 8.0, словарь можно изменять во время перечисления (foreach) только с помощью .Remove и .Clear . Это не относится к другим коллекциям. - person Super Jade; 11.07.2020

Хорошо, так что мне помогло повторение в обратном направлении. Я пытался удалить запись из списка, но выполнял итерацию вверх, и это испортило цикл, потому что записи больше не существовало:

for (int x = myList.Count - 1; x > -1; x--)
{
    myList.RemoveAt(x);
}
person Mark Aven    schedule 22.07.2018

На самом деле мне кажется, что проблема заключается в том, что вы удаляете элементы из списка и ожидаете продолжения чтения списка, как будто ничего не произошло.

Что вам действительно нужно сделать, так это начать с конца и вернуться к началу. Даже если вы удалите элементы из списка, вы сможете продолжить его чтение.

person luc.rg.roy    schedule 23.05.2012
comment
Я не понимаю, какое это имеет значение? Если вы удалите элемент в середине списка, он все равно выдаст ошибку, поскольку элемент, к которому он пытается получить доступ, не может быть найден? - person Zapnologica; 17.07.2014
comment
@Zapnologica, разница в том, что вы не будете перечислять список - вместо того, чтобы делать for / each, вы бы выполняли for / next и получали доступ к нему по целому числу - вы можете определенно измените список a в цикле for / next, но никогда в цикле for / each (потому что for / each перечисляет) - вы также можете сделать это, продвигаясь вперед в цикле for / next, при условии у вас есть дополнительная логика для настройки счетчиков и т. д. - person BrainSlugs83; 08.09.2014

InvalidOperationException- Произошло исключение InvalidOperationException. Он сообщает, что "коллекция была изменена" в цикле foreach

Используйте оператор break после удаления объекта.

ex:

ArrayList list = new ArrayList(); 

foreach (var item in list)
{
    if(condition)
    {
        list.remove(item);
        break;
    }
}
person vivek    schedule 16.03.2017
comment
Хорошее и простое решение, если вы знаете, что в вашем списке есть не более одного элемента, который нужно удалить. - person Tawab Wakil; 09.04.2020

Принятый ответ неточен и в худшем случае неверен. Если изменения были внесены в течение ToList(), вы все равно можете получить ошибку. Помимо lock, производительность и безопасность потоков которой необходимо учитывать, если у вас есть открытый член, правильным решением может быть использование неизменяемые типы.

В общем, неизменяемый тип означает, что вы не можете изменить его однажды созданное состояние. Итак, ваш код должен выглядеть так:

public class SubscriptionServer : ISubscriptionServer
{
    private static ImmutableDictionary<Guid, Subscriber> subscribers = ImmutableDictionary<Guid, Subscriber>.Empty;
    public void SubscribeEvent(string id)
    {
        subscribers = subscribers.Add(Guid.NewGuid(), new Subscriber());
    }
    public void NotifyEvent()
    {
        foreach(var sub in subscribers.Values)
        {
            //.....This is always safe
        }
    }
    //.........
}

Это может быть особенно полезно, если у вас есть публичный участник. Другие классы всегда могут foreach работать с неизменяемыми типами, не беспокоясь об изменении коллекции.

person joe    schedule 05.09.2019

У меня была такая же проблема, и она была решена, когда я использовал цикл for вместо foreach.

// foreach (var item in itemsToBeLast)
for (int i = 0; i < itemsToBeLast.Count; i++)
{
    var matchingItem = itemsToBeLast.FirstOrDefault(item => item.Detach);

   if (matchingItem != null)
   {
      itemsToBeLast.Remove(matchingItem);
      continue;
   }
   allItems.Add(itemsToBeLast[i]);// (attachDetachItem);
}
person Daniel Moreshet    schedule 16.06.2014
comment
Этот код неверен и пропустит некоторые элементы в коллекции, если какой-либо элемент будет удален. Например: у вас есть var arr = [a, b, c], и на первой итерации (i = 0) вы удаляете элемент в позиции 0 (элемент a). После этого все элементы массива переместятся на одну позицию вверх, и массив будет иметь вид [b, c]. Итак, на следующей итерации (i = 1) вы проверите элемент в позиции 1, который будет c, а не b. Это не правильно. Чтобы исправить это, вам нужно двигаться снизу вверх - person Kaspars Ozols; 21.04.2015

Я видел много вариантов для этого, но для меня этот был лучшим.

ListItemCollection collection = new ListItemCollection();
        foreach (ListItem item in ListBox1.Items)
        {
            if (item.Selected)
                collection.Add(item);
        }

Затем просто прокрутите коллекцию.

Имейте в виду, что ListItemCollection может содержать дубликаты. По умолчанию ничто не препятствует добавлению дубликатов в коллекцию. Чтобы избежать дублирования, вы можете сделать это:

ListItemCollection collection = new ListItemCollection();
            foreach (ListItem item in ListBox1.Items)
            {
                if (item.Selected && !collection.Contains(item))
                    collection.Add(item);
            }
person Mike    schedule 04.03.2015
comment
Как этот код предотвратит попадание дубликатов в базу данных. Я написал нечто подобное, и когда я добавляю новых пользователей из списка, если я случайно оставляю выбранным одного из них, который уже есть в списке, создается дублирующаяся запись. У вас есть предложения, @Mike? - person Jamie; 15.03.2018

Хочу указать на другой случай, не отраженный ни в одном из ответов. У меня есть Dictionary<Tkey,TValue> общий доступ в многопоточном приложении, которое использует ReaderWriterLockSlim для защиты операций чтения и записи. Это метод чтения, который вызывает исключение:

public IEnumerable<Data> GetInfo()
{
    List<Data> info = null;
    _cacheLock.EnterReadLock();
    try
    {
        info = _cache.Values.SelectMany(ce => ce.Data); // Ad .Tolist() to avoid exc.
    }
    finally
    {
        _cacheLock.ExitReadLock();
    }
    return info;
}

В целом работает нормально, но время от времени получаются исключения. Проблема заключается в тонкости LINQ: этот код возвращает IEnumerable<Info>, который все еще не перечисляется после выхода из раздела, защищенного блокировкой. Таким образом, он может быть изменен другими потоками перед перечислением, что приведет к исключению. Решение состоит в том, чтобы принудительно выполнить перечисление, например, с помощью .ToList(), как показано в комментарии. Таким образом, перечислимый объект уже пронумерован перед тем, как покинуть защищенный раздел.

Итак, если вы используете LINQ в многопоточном приложении, не забывайте всегда материализовать запросы перед тем, как покинуть защищенные области.

person JotaBe    schedule 27.01.2021

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

для ссылки на исходную ссылку: - https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/

Когда мы используем классы .Net Serialization для сериализации объекта, определение которого содержит тип Enumerable, то есть коллекцию, вы легко получите InvalidOperationException с сообщением «Коллекция была изменена; операция перечисления может не выполняться», когда ваше кодирование выполняется в многопоточных сценариях. Основная причина заключается в том, что классы сериализации будут перебирать коллекцию через перечислитель, поэтому проблема заключается в попытке перебрать коллекцию при ее изменении.

Первое решение, мы можем просто использовать блокировку в качестве решения для синхронизации, чтобы гарантировать, что операция с объектом List может выполняться только из одного потока за раз. Очевидно, вы получите снижение производительности, так как если вы хотите сериализовать коллекцию этого объекта, то для каждого из них будет применена блокировка.

Что ж, .Net 4.0, которая делает работу с многопоточными сценариями удобной. Я обнаружил, что для этой проблемы с сериализацией поля Collection мы можем просто воспользоваться классом ConcurrentQueue (Check MSDN), который является поточно-ориентированной коллекцией FIFO и делает код без блокировки.

Используя этот класс, в его простоте, вещи, которые вам нужно изменить для вашего кода, заменяют им тип Collection, используйте Enqueue, чтобы добавить элемент в конец ConcurrentQueue, удалите этот код блокировки. Или, если сценарий, над которым вы работаете, требует сбора таких вещей, как List, вам понадобится еще несколько кода, чтобы адаптировать ConcurrentQueue к вашим полям.

Кстати, ConcurrentQueue не имеет метода Clear из-за базового алгоритма, который не разрешает атомарную очистку коллекции. поэтому вам придется сделать это самостоятельно, самый быстрый способ - воссоздать новый пустой ConcurrentQueue для замены.

person user8851697    schedule 13.12.2018

Вот конкретный сценарий, требующий особого подхода:

  1. Dictionary перечисляется часто.
  2. Dictionary изменяется нечасто.

В этом сценарии создание копии Dictionary (или Dictionary.Values) перед каждым перечислением может быть довольно дорогостоящим. Моя идея решения этой проблемы состоит в том, чтобы повторно использовать одну и ту же кэшированную копию в нескольких перечислениях и следить за IEnumerator оригинального Dictionary на предмет исключений. Перечислитель будет кэширован вместе с скопированными данными и опрошен перед началом нового перечисления. В случае исключения кешированная копия будет отброшена, и будет создана новая. Вот моя реализация этой идеи:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

public class EnumerableSnapshot<T> : IEnumerable<T>, IDisposable
{
    private IEnumerable<T> _source;
    private IEnumerator<T> _enumerator;
    private ReadOnlyCollection<T> _cached;

    public EnumerableSnapshot(IEnumerable<T> source)
    {
        _source = source ?? throw new ArgumentNullException(nameof(source));
    }

    public IEnumerator<T> GetEnumerator()
    {
        if (_source == null) throw new ObjectDisposedException(this.GetType().Name);
        if (_enumerator == null)
        {
            _enumerator = _source.GetEnumerator();
            _cached = new ReadOnlyCollection<T>(_source.ToArray());
        }
        else
        {
            var modified = false;
            if (_source is ICollection collection) // C# 7 syntax
            {
                modified = _cached.Count != collection.Count;
            }
            if (!modified)
            {
                try
                {
                    _enumerator.MoveNext();
                }
                catch (InvalidOperationException)
                {
                    modified = true;
                }
            }
            if (modified)
            {
                _enumerator.Dispose();
                _enumerator = _source.GetEnumerator();
                _cached = new ReadOnlyCollection<T>(_source.ToArray());
            }
        }
        return _cached.GetEnumerator();
    }

    public void Dispose()
    {
        _enumerator?.Dispose();
        _enumerator = null;
        _cached = null;
        _source = null;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public static class EnumerableSnapshotExtensions
{
    public static EnumerableSnapshot<T> ToEnumerableSnapshot<T>(
        this IEnumerable<T> source) => new EnumerableSnapshot<T>(source);
}

Пример использования:

private static IDictionary<Guid, Subscriber> _subscribers;
private static EnumerableSnapshot<Subscriber> _subscribersSnapshot;

//...(in the constructor)
_subscribers = new Dictionary<Guid, Subscriber>();
_subscribersSnapshot = _subscribers.Values.ToEnumerableSnapshot();

// ...(elsewere)
foreach (var subscriber in _subscribersSnapshot)
{
    //...
}

К сожалению, эта идея в настоящее время не может использоваться с классом Dictionary в .NET Core 3.0, поскольку этот класс не генерирует исключение Коллекция была изменена при перечислении и методы _ 10_ и _ 11_. Все остальные контейнеры, которые я проверял, работают стабильно. Я систематически проверял эти классы: List<T>, Collection<T>, ObservableCollection<T>, HashSet<T>, SortedSet<T>, Dictionary<T,V> и SortedDictionary<T,V>. Только два вышеупомянутых метода класса Dictionary в .NET Core не делают перечисление недействительным.


Обновление: я исправил указанную выше проблему, сравнив также длину кэшированной и исходной коллекции. Это исправление предполагает, что словарь будет передан непосредственно в качестве аргумента конструктору EnumerableSnapshot, и его идентификатор не будет скрыт (например) проекцией типа: dictionary.Select(e => e).ΤοEnumerableSnapshot().


Важно! Указанный выше класс не потокобезопасен. Он предназначен для использования из кода, работающего исключительно в одном потоке.

person Theodor Zoulias    schedule 30.10.2019

Этот способ должен охватывать ситуацию параллелизма, когда функция вызывается снова, пока все еще выполняется (а элементы необходимо использовать только один раз):

 while (list.Count > 0)
 {
    string Item = list[0];
    list.RemoveAt(0);
 
    // do here what you need to do with item
 
 } 
 

Если функция вызывается во время выполнения, элементы не будут повторяться с первого раза, поскольку они будут удалены, как только они будут использованы. Не должно сильно влиять на производительность для небольших списков.

person qfactor77    schedule 12.11.2020

Вы можете скопировать объект словаря подписчиков в тот же тип объекта временного словаря, а затем выполнить итерацию объекта временного словаря с помощью цикла foreach.

person Rezoan    schedule 29.05.2013
comment
(Этот пост, похоже, не дает качественного ответа на вопрос. Измените свой ответ и, либо просто опубликуйте это как комментарий к вопросу). - person sɐunıɔןɐqɐp; 24.06.2018

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

person ford prefect    schedule 04.10.2013