Проблема перечислителя, есть ли способ избежать двух циклов? - PullRequest
3 голосов
/ 18 июня 2010

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

Мне нужно удалить элемент в этом перечислителе, поэтому я не могу использовать «для каждого».Единственный вариант, который я могу придумать, - это получить счет путем итерации по перечислению, а затем запустить нормальный цикл for для удаления элементов.

Кто-нибудь знает способ избежать двух циклов?

Спасибо

[обновление] извините за путаницу, но Андрей ниже в комментариях прав.

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

for each (myProperty in MyProperty)
{
if (checking some criteria here)
   MyProperty.Remove(myProperty)
}

MyProperty - это сторонний класс, который реализует перечислитель и метод удаления.

Ответы [ 11 ]

6 голосов
/ 18 июня 2010

Общий шаблон - сделать что-то вроде этого:

List<Item> forDeletion = new List<Item>();

foreach (Item i in somelist)
   if (condition for deletion) forDeletion.Add(i);

foreach (Item i in forDeletion)
   somelist.Remove(i); //or how do you delete items 
3 голосов
/ 18 июня 2010

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

2 голосов
/ 18 июня 2010

Мне нужно удалить элемент в этом перечислителе

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

foreach (var item in collection) {
    if (item.Equals(toRemove) {
        collection.Remove(toRemove);
        break;      // <== stop iterating!!
    }
}
2 голосов
/ 18 июня 2010

Вы можете создать что-то вроде этого:

      public IEnumerable<item> GetMyList()
    {
        foreach (var x in thirdParty )
        {
            if (x == ignore)
                continue;
            yield return x;
        }

    }
2 голосов
/ 18 июня 2010

Если вы знаете, что это коллекция, вы можете вернуться к:

for (int i = items.Count - 1; i >= 0; i--)
{
   items.RemoveAt(i);
}

В противном случае вам придется сделать две петли.

1 голос
/ 18 июня 2010

Можете ли вы подробнее рассказать об API и вызовах API, которые вы используете?

Если вы получаете IEnumerator<T> или IEnumerable<T>, вы не можете удалить какой-либо элемент из последовательности за перечислителем, потому что нет методасделать это.И, конечно, вы не должны полагаться на приведение полученного объекта вниз, потому что реализация может измениться.(На самом деле, хорошо разработанный API не должен открывать изменяемые объекты, содержащие внутреннее состояние.)

Если вы получаете IList<T> или что-то подобное, вы можете просто использовать нормальный цикл for спереди назад и удалитьэлементы по мере необходимости, потому что нет итератора, состояние которого может быть повреждено.(Здесь правило о выставлении изменяемого состояния должно применяться снова - изменение возвращенной коллекции не должно изменять никакого состояния.)

1 голос
/ 18 июня 2010

Невозможно удалить элемент из перечислителя.Что вы можете сделать, это скопировать или отфильтровать (или оба) содержимое всей последовательности перечисления.Вы можете добиться этого с помощью linq и сделать что-то вроде этого:

   YourEnumerationReturningFunction().Where(item => yourRemovalCriteria);
0 голосов
/ 19 июня 2010

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

0 голосов
/ 18 июня 2010

Чистый, читаемый способ сделать это следующим образом (я предполагаю, что API стороннего контейнера здесь, так как вы его не указали.)

foreach(var delItem in ThirdPartyContainer.Items
                       .Where(item=>ShouldIDeleteThis(item))
                       //or: .Where(ShouldIDeleteThis)
                       .ToArray()) {
    ThirdPartyContainer.Remove(delItem);
}

Вызов .ToArray() гарантирует, что все элементы, подлежащие удалению, были жадно кэшированы до начала итерации foreach.

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

В отличие от этого, итерация в обратном порядке, хотя и не ракетостроение, гораздо более склонна к ошибкам и труднее читать;и он также опирается на внутренние элементы коллекции, такие как неизменный порядок между удалениями (например, лучше не быть двоичной кучей, скажем).Ручное добавление элементов, которые должны быть удалены во временный список, является просто ненужным кодом - вот что .ToArray () прекрасно подойдет: -).

0 голосов
/ 18 июня 2010

Почему не что-то вроде ..

 // you don't want 2 and 3
 IEnumerable<int> fromAPI = Enumerable.Range(0, 10);
 IEnumerable<int> result = fromAPI.Except(new[] { 2, 3 });
...