[c#] C #에서 열거하는 동안 List <T>에서 항목을 제거하는 지능적인 방법

루프에서 열거하는 동안 컬렉션에서 항목을 제거하려는 고전적인 경우가 있습니다.

List<int> myIntCollection = new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);

foreach (int i in myIntCollection)
{
    if (i == 42)
        myIntCollection.Remove(96);    // The error is here.
    if (i == 25)
        myIntCollection.Remove(42);    // The error is here.
}

변경이 발생한 후 반복이 시작될 InvalidOperationException때 열거자가 기본 컬렉션이 변경되는시기를 좋아하지 않기 때문에가 발생합니다.

반복하는 동안 컬렉션을 변경해야합니다. 이를 방지하는 데 사용할 수있는 패턴 이 많이 있지만 그중 어느 것도 좋은 해결책이없는 것 같습니다.

  1. 이 루프 내부를 삭제하지 말고 대신 메인 루프 이후에 처리하는 별도의 “삭제 목록”을 유지하십시오.

    이것은 일반적으로 좋은 해결책이지만 제 경우에는 항목을 실제로 삭제하기 위해 항목을 실제로 삭제하기 위해 코드의 논리 흐름이 변경 될 때까지 항목이 “대기”상태로 즉시 사라져야합니다.

  2. 항목을 삭제하는 대신 항목에 플래그를 설정하고 비활성으로 표시하면됩니다. 그런 다음 패턴 1의 기능을 추가하여 목록을 정리하십시오.

    것이 내 요구 모두를 위해 작동하지만 그것은 것을 의미합니다 많은 코드가 비활성 플래그를 항목에 액세스 할 때마다 시간을 확인하기 위해 변경해야합니다. 이것은 내 취향에 비해 너무 많은 관리입니다.

  3. 어떻게 든 패턴 2의 아이디어를 List<T>. 이 수퍼리스트는 비활성 플래그, 팩트 이후의 객체 삭제를 처리하고 비활성으로 표시된 항목을 열거 소비자에게 노출하지 않습니다. 기본적으로 패턴 2 (및 이후 패턴 1)의 모든 아이디어를 캡슐화합니다.

    이와 같은 클래스가 있습니까? 누구든지 이것에 대한 코드가 있습니까? 아니면 더 좋은 방법이 있습니까?

  4. myIntCollection.ToArray()대신 액세스 myIntCollection하면 문제가 해결되고 루프 내부에서 삭제할 수 있다고 들었습니다 .

    이것은 나에게 나쁜 디자인 패턴처럼 보이거나 괜찮을까요?

세부:

  • 목록에는 많은 항목이 포함되며 그중 일부만 제거합니다.

  • 루프 내에서 모든 종류의 프로세스, 추가, 제거 등을 수행 할 것이므로 솔루션은 상당히 일반적이어야합니다.

  • 삭제해야하는 항목이 루프의 현재 항목 이 아닐 수 있습니다. 예를 들어, 30 개 항목 루프의 항목 10에 있고 항목 6 또는 항목 26을 제거해야 할 수 있습니다.이 때문에 배열을 뒤로 걸어가는 작업이 더 이상 작동하지 않습니다. ;영형(



답변

가장 좋은 해결책은 일반적으로 다음 RemoveAll()방법 을 사용하는 것입니다.

myList.RemoveAll(x => x.SomeProp == "SomeValue");

또는 특정 요소를 제거 해야하는 경우 :

MyListType[] elems = new[] { elem1, elem2 };
myList.RemoveAll(x => elems.Contains(x));

물론 루프가 제거 목적으로 만 사용된다고 가정합니다. 추가 처리 필요한 경우 열거자를 사용하지 않기 때문에 일반적으로 for또는 while루프 를 사용하는 것이 가장 좋은 방법입니다 .

for (int i = myList.Count - 1; i >= 0; i--)
{
    // Do processing here, then...
    if (shouldRemoveCondition)
    {
        myList.RemoveAt(i);
    }
}

뒤로 이동하면 요소를 건너 뛰지 않습니다.

편집에 대한 응답 :

겉보기에 임의의 요소를 제거하려는 경우 가장 쉬운 방법은 제거하려는 요소를 추적 한 다음 한 번에 모두 제거하는 것입니다. 이 같은:

List<int> toRemove = new List<int>();
foreach (var elem in myList)
{
    // Do some stuff

    // Check for removal
    if (needToRemoveAnElement)
    {
        toRemove.Add(elem);
    }
}

// Remove everything here
myList.RemoveAll(x => toRemove.Contains(x));


답변

a를 열거 List<T>하고 제거 해야한다면 a while대신 루프를 사용하는 것이 좋습니다 .foreach

var index = 0;
while (index < myList.Count) {
  if (someCondition(myList[index])) {
    myList.RemoveAt(index);
  } else {
    index++;
  }
}


답변

이 게시물이 오래되었다는 것을 알고 있지만 저에게 효과가있는 것을 공유 할 것이라고 생각했습니다.

열거 할 목록의 복사본을 만든 다음 for each 루프에서 복사 된 값을 처리하고 소스 목록을 사용하여 제거 / 추가 / 무엇이든 할 수 있습니다.

private void ProcessAndRemove(IList<Item> list)
{
    foreach (var item in list.ToList())
    {
        if (item.DeterminingFactor > 10)
        {
            list.Remove(item);
        }
    }
}


답변

목록을 반복해야하고 루프 중에 수정할 수있는 경우 for 루프를 사용하는 것이 좋습니다.

for (int i = 0; i < myIntCollection.Count; i++)
{
    if (myIntCollection[i] == 42)
    {
        myIntCollection.Remove(i);
        i--;
    }
}

물론주의해야합니다. 예를 들어 i항목이 제거 될 때마다 감소합니다. 그렇지 않으면 항목을 건너 뜁니다 (대안은 목록을 뒤로 이동하는 것입니다).

Linq가 있다면 RemoveAlldlev가 제안한대로 사용해야합니다.


답변

목록을 열거 할 때 보관할 목록을 새 목록에 추가합니다. 그런 다음 새 목록을myIntCollection

List<int> myIntCollection=new List<int>();
myIntCollection.Add(42);
List<int> newCollection=new List<int>(myIntCollection.Count);

foreach(int i in myIntCollection)
{
    if (i want to delete this)
        ///
    else
        newCollection.Add(i);
}
myIntCollection = newCollection;


답변

코드를 추가해 보겠습니다.

List<int> myIntCollection=new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);

foreach에있는 동안 목록을 변경하려면 다음을 입력해야합니다. .ToList()

foreach(int i in myIntCollection.ToList())
{
    if (i == 42)
       myIntCollection.Remove(96);
    if (i == 25)
       myIntCollection.Remove(42);
}


답변

도움이 될 수있는 사람들을 위해이 Extension 메서드를 작성하여 조건 자와 일치하는 항목을 제거하고 제거 된 항목 목록을 반환합니다.

    public static IList<T> RemoveAllKeepRemoved<T>(this IList<T> source, Predicate<T> predicate)
    {
        IList<T> removed = new List<T>();
        for (int i = source.Count - 1; i >= 0; i--)
        {
            T item = source[i];
            if (predicate(item))
            {
                removed.Add(item);
                source.RemoveAt(i);
            }
        }
        return removed;
    }